diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4c5403 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,119 @@ +# 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 \ + curl \ + 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; \ + apt-get update; \ + wget -qO /tmp/percona-release.deb "https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb"; \ + apt-get install -y --no-install-recommends /tmp/percona-release.deb; \ + rm -f /tmp/percona-release.deb; \ + 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-pg-tde${PG_MAJOR} \ + percona-postgresql-contrib \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + # 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; \ + 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 +# 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..966e228 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +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 buildx build --pull --platform $(PLATFORM) --load \ + --build-arg PG_MAJOR=$(PG_MAJOR) \ + --build-arg PG_VERSION=$(PG_VERSION) \ + -t $(IMAGE):$(TAG) . + +.PHONY: push +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. +.PHONY: version +version: + docker run --rm --entrypoint postgres $(IMAGE):$(TAG) --version diff --git a/README.md b/README.md index 903d4b1..74f2298 100644 --- a/README.md +++ b/README.md @@ -1 +1,78 @@ -# 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), + `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` + +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-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.