From fa94a13105cc43cdc8d9ff55432a42375a77f23c Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Wed, 15 Jul 2026 08:52:24 +0600 Subject: [PATCH 1/3] Add Percona Server for PostgreSQL + pg_tde image for KubeDB TDE Build Percona Server for PostgreSQL (the fork tde_heap requires) with pg_tde on debian:bookworm, laid out to match the Docker official postgres:-bookworm image so KubeDB's init-container run scripts work unchanged: postgres user at uid/gid 999, binaries on PATH at /usr/lib/postgresql//bin, and the official docker-entrypoint.sh plus gosu borrowed from that image. The build verifies pg_tde_basebackup and pg_tde_rewind are present, which the operator and pg-coordinator depend on for encrypted replica seeding and safe failback. Published as ghcr.io/appscode-images/percona-distribution-postgresql, matching the KubeDB naming convention used for other Percona products. Signed-off-by: Tamal Saha --- Dockerfile | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 34 ++++++++++++++++ README.md | 77 +++++++++++++++++++++++++++++++++++- 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 Makefile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..628ec2b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,113 @@ +# Percona Server for PostgreSQL + pg_tde, laid out to match the Docker official +# `postgres:-bookworm` image so it is a drop-in for KubeDB. +# +# Why a dedicated image: pg_tde's `tde_heap` access method requires the Percona +# Server for PostgreSQL fork, not community PostgreSQL. This image installs the +# Percona distribution from Percona's apt repository while keeping the exact same +# runtime contract KubeDB already relies on for `postgres:-bookworm`: +# * a `postgres` user/group at uid/gid 999 +# * server binaries on PATH at /usr/lib/postgresql//bin +# * the official docker-entrypoint.sh + gosu (borrowed from the upstream image) +# * PGDATA, locale, VOLUME, STOPSIGNAL and EXPOSE identical to upstream +# KubeDB overrides the command with its init-container run scripts at runtime, so +# what matters is the layout, the user, and the binaries, all of which match. + +ARG PG_MAJOR=17 + +# Borrow the proven entrypoint scripts and gosu from the Docker official image so +# we do not re-implement (and drift from) that contract. +FROM postgres:${PG_MAJOR}-bookworm AS upstream + +FROM debian:bookworm-slim + +ARG PG_MAJOR +ENV PG_MAJOR=${PG_MAJOR} +# Optionally pin the exact Percona minor (e.g. 2:17.9-1.bookworm) so the image +# tag can match the installed server version. Empty installs the latest 17.x. +# Find a value with: apt-cache madison percona-postgresql-17 +ARG PG_VERSION= + +LABEL org.opencontainers.image.source="https://github.com/kubedb/postgres-docker" +LABEL percona.package="Percona Server for PostgreSQL" + +# Match the official image: create the postgres user *before* installing the +# server packages so the package postinst adopts uid/gid 999 instead of a +# distro-assigned id. KubeDB runs the pod as runAsUser 999. +RUN set -eux; \ + groupadd -r postgres --gid=999; \ + useradd -r -g postgres --uid=999 --home-dir=/var/lib/postgresql --shell=/bin/bash postgres; \ + mkdir -p /var/lib/postgresql; \ + chown -R postgres:postgres /var/lib/postgresql + +# Base tooling + locale (en_US.UTF-8, same as upstream). +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + gnupg \ + lsb-release \ + locales \ + wget \ + ; \ + localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8; \ + rm -rf /var/lib/apt/lists/* +ENV LANG=en_US.utf8 + +# Enable Percona's apt repo for the PostgreSQL 17 line, then install the Percona +# server, contrib (pg_stat_statements, etc.) and the pg_tde extension. pg_tde is +# a hard dependency of the server package now, but we name it explicitly so the +# intent (and the build) is unambiguous. `create_main_cluster = false` stops +# postgresql-common from spinning up a throwaway cluster during the build. +RUN set -eux; \ + wget -qO /tmp/percona-release.deb "https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb"; \ + dpkg -i /tmp/percona-release.deb; \ + rm -f /tmp/percona-release.deb; \ + apt-get update; \ + percona-release setup ppg-17; \ + apt-get update; \ + mkdir -p /etc/postgresql-common; \ + echo 'create_main_cluster = false' > /etc/postgresql-common/createcluster.conf; \ + PIN=""; [ -n "${PG_VERSION}" ] && PIN="=${PG_VERSION}"; \ + apt-get install -y --no-install-recommends \ + percona-postgresql-${PG_MAJOR}${PIN} \ + percona-postgresql-client-${PG_MAJOR}${PIN} \ + percona-postgresql-${PG_MAJOR}-pg-tde${PIN} \ + percona-postgresql-contrib \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + # sanity check: the Percona server and the pg_tde tooling KubeDB needs + # (pg_tde_basebackup for replica seeding, pg_tde_rewind for failback) must be + # present and on PATH. + /usr/lib/postgresql/${PG_MAJOR}/bin/postgres --version; \ + test -x "/usr/lib/postgresql/${PG_MAJOR}/bin/pg_tde_basebackup"; \ + test -x "/usr/lib/postgresql/${PG_MAJOR}/bin/pg_tde_rewind" + +# Same PATH / PGDATA / run-dir setup as the official image. KubeDB overrides +# PGDATA to its own mount at runtime; this default keeps the image usable +# standalone. +ENV PATH=/usr/lib/postgresql/${PG_MAJOR}/bin:$PATH +ENV PGDATA=/var/lib/postgresql/data +RUN set -eux; \ + install --verbose --directory --owner postgres --group postgres --mode 1777 /var/run/postgresql; \ + install --verbose --directory --owner postgres --group postgres --mode 1777 "$PGDATA" + +VOLUME /var/lib/postgresql/data + +# Reuse the upstream entrypoint contract verbatim (entrypoint + initdb helpers + +# gosu) so behaviour is identical to postgres:-bookworm. +COPY --from=upstream /usr/local/bin/gosu /usr/local/bin/gosu +COPY --from=upstream /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +COPY --from=upstream /usr/local/bin/docker-ensure-initdb.sh /usr/local/bin/docker-ensure-initdb.sh +RUN set -eux; \ + ln -sfT docker-ensure-initdb.sh /usr/local/bin/docker-enforce-initdb.sh; \ + gosu --version; \ + gosu nobody true +ENTRYPOINT ["docker-entrypoint.sh"] + +# postgres uses SIGINT for a fast shutdown; match upstream so orchestrators stop +# it cleanly. +STOPSIGNAL SIGINT +EXPOSE 5432 + +USER 999 +CMD ["postgres"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..482b744 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +SHELL=/bin/bash -o pipefail + +# The KubeDB convention for Percona products is a rebuild published under +# ghcr.io/appscode-images (mirrors percona-xtradb-cluster). +REGISTRY ?= ghcr.io/appscode-images +BIN := percona-distribution-postgresql +IMAGE := $(REGISTRY)/$(BIN) + +# PG_MAJOR is the PostgreSQL major line. TAG should match the installed Percona +# minor; set PG_VERSION to pin it exactly (see README), otherwise the build +# tracks the latest minor of the major line and you should tag accordingly. +PG_MAJOR ?= 17 +TAG ?= 17.9 +PG_VERSION ?= + +# amd64 by default; Percona publishes arm64 too. +PLATFORM ?= linux/amd64 + +.PHONY: container +container: + docker build --pull \ + --build-arg PG_MAJOR=$(PG_MAJOR) \ + --build-arg PG_VERSION=$(PG_VERSION) \ + -t $(IMAGE):$(TAG) . + +.PHONY: push +push: container + docker push $(IMAGE):$(TAG) + +# Convenience: print the Percona server version baked into the built image so the +# tag can be reconciled with the actual minor. +.PHONY: version +version: + docker run --rm --entrypoint postgres $(IMAGE):$(TAG) --version diff --git a/README.md b/README.md index 903d4b1..2e9f1a3 100644 --- a/README.md +++ b/README.md @@ -1 +1,76 @@ -# postgres-docker \ No newline at end of file +# postgres-docker + +Percona Server for PostgreSQL + [`pg_tde`](https://docs.percona.com/pg-tde/) +container image for KubeDB, published as +`ghcr.io/appscode-images/percona-distribution-postgresql:`. + +## Why this image + +KubeDB's Transparent Data Encryption (TDE) support uses Percona's `pg_tde` +extension. Its `tde_heap` access method requires the **Percona Server for +PostgreSQL** fork, not community PostgreSQL, so TDE cannot run on the stock +`postgres:-bookworm` image. This repository builds a Percona server image +that keeps the exact same runtime contract KubeDB already relies on, so the +existing init-container run scripts work against it unchanged: + +- `postgres` user and group at **uid/gid 999** (matches + `spec.securityContext.runAsUser: 999`) +- server binaries on `PATH` at `/usr/lib/postgresql//bin` +- the TDE tooling KubeDB needs: `pg_tde_basebackup` (encrypted replica seeding) + and `pg_tde_rewind` (safe failback), verified present at build time +- the Docker official `docker-entrypoint.sh`, initdb helpers and `gosu`, borrowed + verbatim from `postgres:-bookworm` via a multi-stage copy +- identical `PGDATA`, locale, `VOLUME`, `STOPSIGNAL SIGINT` and `EXPOSE 5432` + +At runtime KubeDB overrides the command with its own scripts and points `PGDATA` +at its mount, so the layout, user and binaries are what matter, and they all +match the official image. + +## Catalog wiring + +Reference it from the `PostgresVersion` catalog entry: + +```yaml +spec: + db: + baseOS: bookworm + image: ghcr.io/appscode-images/percona-distribution-postgresql:17.9 + distribution: Percona + securityContext: + runAsAnyNonRoot: true + runAsUser: 999 + tde: + supported: true + extensionName: pg_tde +``` + +Note this differs from pointing at the upstream `percona/percona-distribution-postgresql` +image, which is UBI-based (uid 26, `/usr/pgsql-17/bin`, `PGDATA=/data/db`) and +does not match the KubeDB runtime. + +## Build + +```bash +# latest minor of the 17 line +make container TAG=17 + +# pin an exact Percona minor so the tag is truthful +# find the version string with: apt-cache madison percona-postgresql-17 +make container PG_VERSION=2:17.9-1.bookworm TAG=17.9 + +# publish +make push TAG=17.9 + +# confirm the baked-in server version matches the tag +make version TAG=17.9 +``` + +`make` variables: `REGISTRY` (default `ghcr.io/appscode-images`), `PG_MAJOR` +(default `17`), `TAG`, `PG_VERSION` (empty tracks latest minor). + +## Notes + +- Percona ships `pg_tde` as a hard dependency of the server package now, but the + Dockerfile installs `percona-postgresql--pg-tde` explicitly for clarity. +- `shared_preload_libraries` is intentionally left empty in the image; KubeDB + composes it (with `pg_tde` first) in its start scripts when TDE is enabled. From 04a4e0db67113b6811bb857a1a69d92827f327fb Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Wed, 15 Jul 2026 13:45:24 +0600 Subject: [PATCH 2/3] Fix the Percona image build: cross-arch push, curl dep, pg_tde package name Three fixes found building the image for a real amd64 cluster: - Makefile declared PLATFORM but the container target used plain docker build, so it produced a host-arch image. Cross-build via docker buildx --platform and push directly with --push. - The base layer lacked curl, which percona-release depends on, so dpkg -i of the repo package failed. Add curl and install the .deb via apt-get install so dependencies resolve. - The pg_tde package is percona-pg-tde (for example percona-pg-tde17), not percona-postgresql--pg-tde. It ships the pg_tde CLI tools (pg_tde_basebackup, pg_tde_rewind, ...) at /usr/lib/postgresql//bin, which the build asserts. Verified: the image builds Percona Server for PostgreSQL 17.10.2 with pg_tde and the CLI tools present. Signed-off-by: Tamal Saha --- Dockerfile | 7 ++++--- Makefile | 9 ++++++--- README.md | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 628ec2b..03b5951 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,6 +44,7 @@ RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates \ + curl \ gnupg \ lsb-release \ locales \ @@ -59,10 +60,10 @@ ENV LANG=en_US.utf8 # intent (and the build) is unambiguous. `create_main_cluster = false` stops # postgresql-common from spinning up a throwaway cluster during the build. RUN set -eux; \ + apt-get update; \ wget -qO /tmp/percona-release.deb "https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb"; \ - dpkg -i /tmp/percona-release.deb; \ + apt-get install -y --no-install-recommends /tmp/percona-release.deb; \ rm -f /tmp/percona-release.deb; \ - apt-get update; \ percona-release setup ppg-17; \ apt-get update; \ mkdir -p /etc/postgresql-common; \ @@ -71,7 +72,7 @@ RUN set -eux; \ apt-get install -y --no-install-recommends \ percona-postgresql-${PG_MAJOR}${PIN} \ percona-postgresql-client-${PG_MAJOR}${PIN} \ - percona-postgresql-${PG_MAJOR}-pg-tde${PIN} \ + percona-pg-tde${PG_MAJOR} \ percona-postgresql-contrib \ ; \ rm -rf /var/lib/apt/lists/*; \ diff --git a/Makefile b/Makefile index 482b744..966e228 100644 --- a/Makefile +++ b/Makefile @@ -18,14 +18,17 @@ PLATFORM ?= linux/amd64 .PHONY: container container: - docker build --pull \ + docker buildx build --pull --platform $(PLATFORM) --load \ --build-arg PG_MAJOR=$(PG_MAJOR) \ --build-arg PG_VERSION=$(PG_VERSION) \ -t $(IMAGE):$(TAG) . .PHONY: push -push: container - docker push $(IMAGE):$(TAG) +push: + docker buildx build --pull --platform $(PLATFORM) --push \ + --build-arg PG_MAJOR=$(PG_MAJOR) \ + --build-arg PG_VERSION=$(PG_VERSION) \ + -t $(IMAGE):$(TAG) . # Convenience: print the Percona server version baked into the built image so the # tag can be reconciled with the actual minor. diff --git a/README.md b/README.md index 2e9f1a3..0758ffd 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,6 @@ make version TAG=17.9 ## Notes - Percona ships `pg_tde` as a hard dependency of the server package now, but the - Dockerfile installs `percona-postgresql--pg-tde` explicitly for clarity. + Dockerfile installs `percona-pg-tde` explicitly for clarity. - `shared_preload_libraries` is intentionally left empty in the image; KubeDB composes it (with `pg_tde` first) in its start scripts when TDE is enabled. From 1324b3125081b9bb156eedbf53e0068ba1e71cbd Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Wed, 15 Jul 2026 19:25:29 +0600 Subject: [PATCH 3/3] assert pg_tde_waldump and pg_tde_resetwal at build time The coordinator's failback path on a WAL-encrypted cluster uses pg_tde_waldump (checkpoint-end decode) and pg_tde_resetwal in addition to pg_tde_basebackup and pg_tde_rewind. Plain pg_waldump/pg_resetwal cannot read encrypted WAL, so a Percona package that dropped these tools would silently break TDE HA failback. Assert all four in the build-time sanity check. Signed-off-by: Tamal Saha --- Dockerfile | 15 ++++++++++----- README.md | 6 ++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03b5951..e4c5403 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,12 +76,17 @@ RUN set -eux; \ percona-postgresql-contrib \ ; \ rm -rf /var/lib/apt/lists/*; \ - # sanity check: the Percona server and the pg_tde tooling KubeDB needs - # (pg_tde_basebackup for replica seeding, pg_tde_rewind for failback) must be - # present and on PATH. + # sanity check: the Percona server and the pg_tde tooling KubeDB needs must be + # present and on PATH. KubeDB uses pg_tde_basebackup for replica seeding, + # pg_tde_rewind for failback, and pg_tde_waldump / pg_tde_resetwal in the + # coordinator's failback WAL handling on an encrypted cluster (the plain tools + # cannot read encrypted WAL). Missing any of these silently breaks TDE HA, so + # assert them at build time. /usr/lib/postgresql/${PG_MAJOR}/bin/postgres --version; \ - test -x "/usr/lib/postgresql/${PG_MAJOR}/bin/pg_tde_basebackup"; \ - test -x "/usr/lib/postgresql/${PG_MAJOR}/bin/pg_tde_rewind" + for _bin in pg_tde_basebackup pg_tde_rewind pg_tde_waldump pg_tde_resetwal; do \ + test -x "/usr/lib/postgresql/${PG_MAJOR}/bin/${_bin}" \ + || { echo "FATAL: required pg_tde tool ${_bin} not found in the image"; exit 1; }; \ + done # Same PATH / PGDATA / run-dir setup as the official image. KubeDB overrides # PGDATA to its own mount at runtime; this default keeps the image usable diff --git a/README.md b/README.md index 0758ffd..74f2298 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,10 @@ existing init-container run scripts work against it unchanged: - `postgres` user and group at **uid/gid 999** (matches `spec.securityContext.runAsUser: 999`) - server binaries on `PATH` at `/usr/lib/postgresql//bin` -- the TDE tooling KubeDB needs: `pg_tde_basebackup` (encrypted replica seeding) - and `pg_tde_rewind` (safe failback), verified present at build time +- the TDE tooling KubeDB needs: `pg_tde_basebackup` (encrypted replica seeding), + `pg_tde_rewind` (safe failback), and `pg_tde_waldump` / `pg_tde_resetwal` (the + coordinator's failback WAL handling on an encrypted cluster), all verified + present at build time - the Docker official `docker-entrypoint.sh`, initdb helpers and `gosu`, borrowed verbatim from `postgres:-bookworm` via a multi-stage copy - identical `PGDATA`, locale, `VOLUME`, `STOPSIGNAL SIGINT` and `EXPOSE 5432`