phpPgAdmin on Kubernetes
Production deployment for the brunner.ninja phpPgAdmin service. The repository uses a small private image derived from the shared PHP/Apache base, a single plain Kubernetes manifest, Traefik ingress, health probes, zero-unavailable rolling updates, and restricted Gitea Actions deployment credentials.
Architecture
- The repository image adds phpPgAdmin to the same Apache/PHP layer used by the homepage deployments.
- CI deploys the image by its immutable Git commit tag rather than
latest. default/phppgadmin-configmounts the complete multi-server configuration at/etc/phppgadmin/config.inc.php.- A ClusterIP Service exposes TCP port 80 to Traefik.
https://phppgadmin.brunner.ninjaredirects to the image's native/phppgadmin/path.- One replica uses
maxUnavailable: 0,maxSurge: 1, and five seconds of minimum readiness. The old pod remains available until the replacement has passed its HTTP probe continuously for five seconds. - CI places the Git commit in a pod-template annotation. This forces a safe
rollout when a ConfigMap-only change would otherwise leave a
subPathmount unchanged in the running container. - Startup, readiness, and liveness probes request
/phppgadmin/over HTTP.
The deployment is stateless. phpPgAdmin does not store database passwords or sessions on a PVC; users authenticate directly to the selected PostgreSQL server. The ConfigMap lists server addresses but contains no credentials.
Container image
The Dockerfile installs the verified phpPgAdmin 7.14.7 release directly on
registry.brunner.ninja/feedc0de/php-apache. The common image already provides
Apache, PHP, PostgreSQL/PDO PostgreSQL, and OPcache, so Kubernetes nodes share
those layers with the PHP homepage deployments. The phpPgAdmin layer contains
only the application and its Apache alias configuration. Its base tag is the
same immutable php-apache Git commit used by the homepage values files.
The release archive is checked against the SHA-256 published by the Arch User
Repository package before extraction. phpPgAdmin is exposed at
/phppgadmin/, and the existing ConfigMap is mounted at
/etc/phppgadmin/config.inc.php.
Repository contents
phppgadmin.yaml: ConfigMap, Deployment, Service, Ingress, and Traefik root redirect Middleware.install.sh: server-side dry-run, apply, and rollout verification.test.sh: local script and Kubernetes schema validation.ci-deployer.yaml: restricted Gitea Actions ServiceAccount, Role, binding, and revocable token.create-ci-kubeconfig.sh: one-time CI identity bootstrap.Dockerfileandphppgadmin.conf: small phpPgAdmin layer and Apache alias on the shared php-apache image..gitea/workflows/deploy.yml: image build and validation on every push and pull request; image publishing and deployment after a successfulmainrun.
phpPgAdmin is intentionally not deployed through the generic PHP homepage Helm chart. It is stateless, has no CephFS web root, and needs its own ConfigMap and Traefik redirect Middleware.
Prerequisites
- Kubernetes and kubectl.
- The unified
registry.brunner.ninja/feedc0de/php-apacheimage in Quay. - The
quay-pull-secretimage pull secret in thedefaultnamespace. - Traefik with the
traefik.io/v1alpha1Middleware CRD and ingress classtraefik. - The existing Traefik
websecureentrypoint and default*.brunner.ninjaTLS certificate. - Network connectivity from the pod to every PostgreSQL hostname listed in the ConfigMap.
Another cluster operator can reuse the manifest by changing the namespace, database server list, ingress hostname, ingress class, and redirect Middleware.
Install manually
With an administrator kubeconfig active, run:
./install.sh
The script performs a server-side dry-run, applies all resources, and waits up to ten minutes for the Deployment. Existing resource names are preserved, so the Service keeps its ClusterIP and the change rolls out in place.
Verify both application paths:
curl -I https://phppgadmin.brunner.ninja/
curl -I https://phppgadmin.brunner.ninja/phppgadmin/
The first should redirect permanently and the second should return the phpPgAdmin application.
Configure PostgreSQL servers
Edit config.inc.php inside the ConfigMap in phppgadmin.yaml. Each server
entry contains a display name, service DNS name, port, SSL mode, and default
database. Do not put PostgreSQL usernames or passwords into the ConfigMap or
Git repository.
The file is mounted using subPath, so Kubernetes does not update it inside an
already-running pod. The CI workflow replaces the manifest's manual revision
annotation with the Git commit SHA before applying it, which safely rolls the
pod for every merged configuration change. A manual ./install.sh changes the
annotation back to manual and likewise triggers a rollout after a CI deploy.
Validate locally
./test.sh
The test checks both shell scripts and validates the Kubernetes resources with kubeconform. The Traefik Middleware is reported as skipped because kubeconform's standard schema catalog does not contain cluster-specific CRDs; the Kubernetes API validates it during the server-side dry-run.
Automatic deployment with Gitea Actions
The workflow validates every push and pull request. A successful push to
main applies the complete phppgadmin.yaml, including ConfigMap, Deployment,
Service, Ingress, and Middleware, then polls the Deployment for up to ten
minutes.
The workflow builds and smoke-tests the image for every push and pull request.
On main it pushes the immutable Git commit tag plus latest, substitutes the
immutable tag in the deployment manifest, and deploys it. It requires these
Gitea Actions secrets:
QUAY_USERNAME: Quay robot-account username with read/write access.QUAY_TOKEN: token for that robot account.KUBE_CONFIG_BASE64: the restricted phpPgAdmin deployment kubeconfig, base64-encoded on one line.
One-time CI setup
-
Run
./install.shwith an administrator identity so every application resource already exists. -
Run:
./create-ci-kubeconfig.sh -
Copy only the final base64 line into the Gitea repository Actions secret named
KUBE_CONFIG_BASE64. -
Protect
main, require successful validation before merging, and review manifest and workflow changes. -
Push a harmless README change and confirm the deployment job succeeds.
The workflow matches the existing brunner.ninja runner: Kubernetes API URL
https://host.containers.internal:6443, with TLS server name 192.168.0.2.
Other clusters must change those values.
CI can get, patch, and update only these existing named objects:
- ConfigMap
phppgadmin-config; - Deployment and Service
phppgadmin; - Ingress
phppgadmin; - Middleware
phppgadmin-root-redirect.
It cannot create or delete resources and cannot call the Kubernetes Secrets
API. Revoke it by deleting default/phppgadmin-deployer-token and the Gitea
secret. Rerunning the wrapper creates a replacement token.
Kubernetes RBAC cannot constrain individual fields inside an allowed
Deployment patch. A malicious change merged into main could alter the pod
specification. Branch protection and review therefore remain part of the
security boundary.
Internet exposure
This instance is reachable from the public internet and presents direct
PostgreSQL username/password authentication. Use long, unique database
passwords, restrictive PostgreSQL roles and pg_hba.conf rules, TLS for remote
database connections, and external rate limiting or access control where
appropriate. phpPgAdmin's extra_login_security remains enabled in the
committed configuration.