From d912e4f623da6d2d27d889ab60e73034041f2fbc Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Thu, 10 Sep 2026 13:28:15 +0200 Subject: [PATCH] Build phpPgAdmin on unified PHP base --- .dockerignore | 5 +++ .gitea/workflows/deploy.yml | 80 ++++++++++++++++++++++++++++++++++++- Dockerfile | 23 +++++++++++ README.md | 66 +++++++++++++++--------------- phppgadmin.conf | 8 ++++ phppgadmin.yaml | 9 +++-- 6 files changed, 151 insertions(+), 40 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 phppgadmin.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..194307a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.gitea +*.md +*.sh +*.yaml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 9d19b42..88a9c20 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -5,6 +5,7 @@ on: pull_request: env: + IMAGE: registry.brunner.ninja/feedc0de/phppgadmin KUBECONFORM_VERSION: v0.7.0 KUBECTL_VERSION: v1.36.3 KUBERNETES_API: https://host.containers.internal:6443 @@ -12,6 +13,74 @@ env: NAMESPACE: default jobs: + image: + name: Build, test and publish image + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Log in to Quay + uses: docker/login-action@v3 + with: + registry: registry.brunner.ninja + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_TOKEN }} + + - name: Build image + env: + COMMIT_SHA: ${{ gitea.sha }} + run: | + set -euo pipefail + docker build \ + --pull \ + --load \ + --label "org.opencontainers.image.base.name=registry.brunner.ninja/feedc0de/php-apache:f76304f1014f15354ec05bba7eaca376993afe72" \ + --label "org.opencontainers.image.revision=${COMMIT_SHA}" \ + --label "org.opencontainers.image.source=https://code.brunner.ninja/feedc0de/phppgadmin-docker" \ + --tag "${IMAGE}:${COMMIT_SHA}" \ + . + + - name: Smoke-test image + env: + COMMIT_SHA: ${{ gitea.sha }} + run: | + set -euo pipefail + docker run --rm --entrypoint httpd "${IMAGE}:${COMMIT_SHA}" -t + docker run --rm --entrypoint php "${IMAGE}:${COMMIT_SHA}" -r ' + foreach (["pgsql", "pdo_pgsql", "mbstring", "Zend OPcache"] as $extension) { + if (!extension_loaded($extension)) { + fwrite(STDERR, "Missing PHP extension: {$extension}\n"); + exit(1); + } + } + ' + container_id="$(docker run --detach --publish 127.0.0.1::80 "${IMAGE}:${COMMIT_SHA}")" + trap 'docker rm --force "${container_id}" >/dev/null 2>&1 || true' EXIT + port="$(docker inspect --format '{{ (index (index .NetworkSettings.Ports "80/tcp") 0).HostPort }}' "${container_id}")" + for attempt in {1..30}; do + if curl --fail --silent --show-error \ + --output "${RUNNER_TEMP}/phppgadmin.html" \ + "http://127.0.0.1:${port}/phppgadmin/" \ + && grep -Fq 'phpPgAdmin' "${RUNNER_TEMP}/phppgadmin.html"; then + exit 0 + fi + sleep 1 + done + docker logs "${container_id}" + exit 1 + + - name: Push image + if: gitea.ref == 'refs/heads/main' + env: + COMMIT_SHA: ${{ gitea.sha }} + run: | + set -euo pipefail + docker push "${IMAGE}:${COMMIT_SHA}" + docker tag "${IMAGE}:${COMMIT_SHA}" "${IMAGE}:latest" + docker push "${IMAGE}:latest" + validate: name: Validate manifests runs-on: ubuntu-latest @@ -34,7 +103,9 @@ jobs: deploy: name: Deploy to Kubernetes if: gitea.ref == 'refs/heads/main' - needs: validate + needs: + - image + - validate runs-on: ubuntu-latest steps: - name: Check out source @@ -70,9 +141,14 @@ jobs: export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" sed \ "s|deployment.brunner.ninja/revision: manual|deployment.brunner.ninja/revision: ${COMMIT_SHA}|" \ - phppgadmin.yaml > "${RUNNER_TEMP}/phppgadmin.yaml" + phppgadmin.yaml \ + | sed \ + "s|registry.brunner.ninja/feedc0de/phppgadmin:latest|${IMAGE}:${COMMIT_SHA}|" \ + > "${RUNNER_TEMP}/phppgadmin.yaml" grep -Fq "deployment.brunner.ninja/revision: ${COMMIT_SHA}" \ "${RUNNER_TEMP}/phppgadmin.yaml" + grep -Fq "image: ${IMAGE}:${COMMIT_SHA}" \ + "${RUNNER_TEMP}/phppgadmin.yaml" "${RUNNER_TEMP}/kubectl" apply \ --dry-run=server \ --validate=false \ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7acf88a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +ARG PHP_APACHE_TAG=f76304f1014f15354ec05bba7eaca376993afe72 +FROM registry.brunner.ninja/feedc0de/php-apache:${PHP_APACHE_TAG} + +ARG PHPPGADMIN_VERSION=7.14.7 +ARG PHPPGADMIN_SHA256=7c0e89922abcf88fd81a52590930f3b5121032d1d37234a54a8f6415152dc292 + +RUN curl --fail --show-error --location \ + --output /tmp/phppgadmin.tar.gz \ + "https://github.com/ReimuHakurei/phpPgAdmin/archive/refs/tags/v${PHPPGADMIN_VERSION}-mod.tar.gz" \ + && printf '%s %s\n' "${PHPPGADMIN_SHA256}" /tmp/phppgadmin.tar.gz | sha256sum --check \ + && install -d /usr/share/webapps/phppgadmin /etc/phppgadmin \ + && tar --extract --gzip --file /tmp/phppgadmin.tar.gz \ + --strip-components=1 --directory /usr/share/webapps/phppgadmin \ + && cp /usr/share/webapps/phppgadmin/conf/config.inc.php-dist \ + /etc/phppgadmin/config.inc.php \ + && rm -f /usr/share/webapps/phppgadmin/conf/config.inc.php \ + && ln -s /etc/phppgadmin/config.inc.php \ + /usr/share/webapps/phppgadmin/conf/config.inc.php \ + && rm /tmp/phppgadmin.tar.gz + +COPY phppgadmin.conf /etc/httpd/conf/extra/phppgadmin.conf + +RUN printf '%s\n' 'Include conf/extra/phppgadmin.conf' >> /etc/httpd/conf/httpd.conf diff --git a/README.md b/README.md index b994d04..6ac5bfd 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # phpPgAdmin on Kubernetes Production deployment for the brunner.ninja phpPgAdmin service. The repository -uses a maintained public container image, a single plain Kubernetes manifest, -Traefik ingress, health probes, zero-unavailable rolling updates, and restricted -Gitea Actions deployment credentials. +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 -- `tozd/phppgadmin` supplies phpPgAdmin, Nginx, PHP, and PostgreSQL client tools. -- The image is pinned to an immutable multi-platform manifest digest rather - than a mutable tag. +- 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-config` mounts the complete multi-server configuration at `/etc/phppgadmin/config.inc.php`. - A ClusterIP Service exposes TCP port 80 to Traefik. @@ -27,29 +27,19 @@ 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. -## Why there is no Dockerfile +## Container image -This repository previously built phpPgAdmin from the Arch User Repository on -top of a private base image and pushed the result to the private Quay registry. -That duplicated public image storage and made every repository commit rebuild -an otherwise unchanged application. +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 current deployment instead uses the actively maintained public -[`tozd/phppgadmin`](https://gitlab.com/tozd/docker/phppgadmin) image. It supports -the same configuration-file mount used here. The pinned image contains -phpPgAdmin 7.14.7, which is also the current AUR package version at the time of -this repository conversion. - -The committed image is: - -```text -tozd/phppgadmin@sha256:4ee690768d54b0c057dbc4ed5c1baa152c1a5ef0ebd6eb12f1e310e545eb4489 -``` - -It was inspected and smoke-tested before deployment. Pinning prevents an -upstream rebuild from silently changing the running software. To upgrade, -inspect and test the new upstream digest, replace the image line in -`phppgadmin.yaml`, and merge the change through the normal deployment workflow. +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 @@ -60,16 +50,20 @@ inspect and test the new upstream digest, replace the image line in - `ci-deployer.yaml`: restricted Gitea Actions ServiceAccount, Role, binding, and revocable token. - `create-ci-kubeconfig.sh`: one-time CI identity bootstrap. -- `.gitea/workflows/deploy.yml`: validation on every push and pull request; - deployment after successful validation of `main`. +- `Dockerfile` and `phppgadmin.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 successful `main` run. -There is no Helm chart, Kustomize overlay, Argo CD application, private image, -or container build pipeline. +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. -- An amd64 worker node. The pinned upstream image currently publishes amd64. +- The unified `registry.brunner.ninja/feedc0de/php-apache` image in Quay. +- The `quay-pull-secret` image pull secret in the `default` namespace. - Traefik with the `traefik.io/v1alpha1` Middleware CRD and ingress class `traefik`. - The existing Traefik `websecure` entrypoint and default @@ -133,9 +127,13 @@ The workflow validates every push and pull request. A successful push to Service, Ingress, and Middleware, then polls the Deployment for up to ten minutes. -No image is built or pushed. The workflow requires only one Gitea Actions -secret: +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. diff --git a/phppgadmin.conf b/phppgadmin.conf new file mode 100644 index 0000000..8a0e12b --- /dev/null +++ b/phppgadmin.conf @@ -0,0 +1,8 @@ +Alias /phppgadmin "/usr/share/webapps/phppgadmin" + + + DirectoryIndex index.php + AllowOverride All + Options FollowSymLinks + Require all granted + diff --git a/phppgadmin.yaml b/phppgadmin.yaml index d430530..91195f8 100644 --- a/phppgadmin.yaml +++ b/phppgadmin.yaml @@ -241,10 +241,9 @@ spec: terminationGracePeriodSeconds: 30 containers: - name: phppgadmin - # Public, maintained image pinned to the verified multi-platform - # manifest digest published on 2026-08-08. - image: tozd/phppgadmin@sha256:4ee690768d54b0c057dbc4ed5c1baa152c1a5ef0ebd6eb12f1e310e545eb4489 - imagePullPolicy: IfNotPresent + # CI replaces latest with the immutable source commit tag on main. + image: registry.brunner.ninja/feedc0de/phppgadmin:latest + imagePullPolicy: Always ports: - name: http containerPort: 80 @@ -278,6 +277,8 @@ spec: - name: phppgadmin-config configMap: name: phppgadmin-config + imagePullSecrets: + - name: quay-pull-secret --- apiVersion: v1 kind: Service