PostgreSQL community images address a real gap in how a Kubernetes database operator earns your trust. Running a database operator on Kubernetes means trusting two things: the code, and the container images the operator pulls. The code is on GitHub, easy to inspect, easy to fork. The container images, the registry that hosts them, and the license that governs them all sit with the vendor, and any of those three can change without the source repository changing at all. Starting with Percona Operator for PostgreSQL 3.0.0, you can run the operator against community images you build yourself from the official PostgreSQL packages on download.postgresql.org, in a registry you control.
TL;DR
- Community Docker Images: tech preview in PGO 3.0.0, official in 3.1.0. Point the operator at upstream-built PostgreSQL images instead of the Percona Distribution images, in a registry you control.
- You build them yourself from the official PostgreSQL source. The Dockerfiles pull packages from download.postgresql.org (the PGDG repositories), so a security-conscious team can audit the chain end to end.
- Trade-offs are intentional. Distribution-only features like TDE do not exist in a community-built image; if you depend on those, run the distribution image.
In this post:
- How open source gets diluted in practice
- Why distributions exist anyway, honestly
- How Community Docker Images work
- Limits of the upstream path
- What to try, what to tell us
How open source gets diluted
Open source has changed in the last few years, and not always for the better. Companies have learned that you can keep a project's source code fully open and still capture most of the lock-in by quietly closing the parts that matter in production: the release artifacts, the container images, the supported OS list, the certified Kubernetes distributions, the marketplace listings.
Same project, closed artifacts
You can have a fully community CNCF project that does not appear on the Red Hat Marketplace except as a paid Enterprise edition. Similarly, you can have a vendor that ships one packaging in the community and a richer one in Enterprise with the features you actually need in production. The license still says "open source." The practical experience says "you depend on us." And the source repository's license is not the only license that matters here: a vendor can change the license, the trademark policy, or the distribution terms on the container images alone, while leaving the source repository untouched. That has happened in the PostgreSQL operator space recently, and the community noticed.
Why the community is right to be wary
Nobody outside the vendor can predict when a license will change, when a feature will move behind a paywall, or when an external contribution will get rejected because it competes with an Enterprise feature. Recent history has plenty of examples and the PostgreSQL community has been paying attention. When this community resists vendor-controlled distributions, it is not nostalgia. It is a rational read of where things have gone before.
I work on Percona's PostgreSQL operator, so I see this conversation from the vendor side. The skepticism is fair. The honest question for us is what to do about it.
Why distributions exist anyway
Acknowledging the community's concerns does not mean distributions are pointless. There are real reasons to ship one, and pretending otherwise makes for bad blog posts.
What a distribution buys you
A vendor-built distribution lets the vendor:
- Control the build process, dependencies, and defaults so they fit a specific user shape.
- Ship hotfixes faster, because the whole release path sits in one place.
- Fork PostgreSQL itself when something the upstream community will not accept, or can take years to accept, matters to customers, such as Transparent Data Encryption.
- For a Kubernetes operator, ship images with exactly the tools and extensions the operator supports, and skip everything else. The CVE surface stays smaller.
- Give QA and Service teams a predictable environment. "We support extensions A, B, C and not D, X, Z" is only honest if QA actually exercises A, B, C and the Service team can work with them in production env.
- Give customers one accountable party for the full release cycle, from hotfix through package availability. Some teams explicitly need that contract for compliance and audit reasons.
- And yes, less positive reasons that we covered above also apply, which is exactly the part the community keeps pointing at.
The trade-off you accept
If you run the vendor distribution, you accept that the vendor's registry, image policy, and supported-extension matrix become part of your stack. If the vendor changes any of that, your operator deployment changes with it. That is not hypothetical for users who have lived through it on other products.
So the real question is whether you can keep the benefits a distribution provides for the users who want them, while leaving an honest, supported door open for users who do not. That is the door PGO 3.0.0 opens as a tech preview, with PGO 3.1.0 making it part of the regular release cycle.
Community Docker Images in PGO
Starting with Percona Operator for PostgreSQL 3.0.0, the operator can run against images built from upstream PostgreSQL packages, not just the Percona Distribution images. This is what we are calling Community Docker Images. In 3.0.0, the feature ships as a tech preview. In 3.1.0, these images become part of our official release cycle and are fully documented.
One of the main advantages of Community Docker Images is that the community can request or contribute any extension that does not exist in the official Percona PostgreSQL distribution. TimescaleDB and Citus are the first examples: the community asked for them, and we shipped both in the Community Images set from day one.
How it works
The operator does not care where the image came from, as long as the image meets the operator's runtime expectations (pg_ctl, the expected binary layout, the directory structure the operator manages). Therefore, PGO 3.0.0 publishes the build manifests and Dockerfiles for community images so anyone can produce a working image set from upstream packages alone.
A typical CR using a community image looks like this:
apiVersion: pgv2.percona.com/v2
kind: PerconaPGCluster
metadata:
name: cluster1
spec:
image: registry.example.com/postgresql-community:17
postgresVersion: 17
proxy:
pgBouncer:
image: registry.example.com/pgbouncer-community:1.23
backups:
pgbackrest:
image: registry.example.com/pgbackrest-community:2.51
# other spec fields unchanged from a normal CR
The fields that change are spec.image, spec.proxy.pgBouncer.image, and spec.backups.pgbackrest.image. You can build and publish all three images under your own registry, with your own tags if that helps you track versions. The operator drives the rest of the deployment the same way it always has: instances, backups, replication, monitoring, all of it.
What ships in each image
Each Community Docker Image is a thin layer over the chosen base (UBI9 or UBI8) plus the packages the operator needs for that role. Where you see {N}, substitute the PostgreSQL major you build for (17, 18, and so on).
postgres image (e.g. postgres17):
| Package | Role |
|---|---|
postgresql{N}-server |
PostgreSQL server |
postgresql{N}-contrib |
contrib modules |
pg_repack_{N} |
online table/index reorganization |
pgaudit_{N} |
audit logging |
set_user_{N} |
privilege escalation control |
pgvector_{N} |
vector similarity search |
wal2json_{N} |
WAL to JSON logical decoding |
pg_cron_{N} |
in-database cron scheduler |
pgbackrest |
backup/restore tool |
patroni |
HA cluster manager |
timescaledb-2-postgresql-{N} |
time-series extension (x86_64 only; EL9 only for PG18) |
citus_{N} |
distributed PostgreSQL (PG16+ only) |
pgbackrest image:
| Package | Role |
|---|---|
pgbackrest |
backup/restore tool only |
pgbouncer image:
| Package | Role |
|---|---|
pgbouncer |
connection pooler only |
The split is intentional. The postgres image ships the full operator-aware runtime; the backup and proxy images stay minimal. As a result, the operator's components stay in separate failure domains and shrinks the attack surface of each container.
Building your own image
The published build flow is intentionally short. Specifically, the goal is for a security-conscious team to read it end to end in one sitting, audit the dependencies, and reproduce the build on their own infrastructure. We are not asking anyone to trust a black box. In fact, the build pulls PostgreSQL from download.postgresql.org, the official package repositories maintained by the PostgreSQL Global Development Group (PGDG), so the trust chain runs from PGDG to your build to your registry to your operator, with no vendor in the middle. The Dockerfile, the package list, and a sample CI job for keeping the image current are available with the 3.0.0 tech preview, and become part of the official documentation in 3.1.0.
Once your image is in your registry, you point the operator at it. Day-to-day operations look identical.
How to build the images
The Dockerfile, the package list, and a sample CI job ship in percona-docker/postgresql-containers/community. The build is a regular make target on top of docker buildx, so you can run it on any multi-platform builder.
# Prerequisites: docker buildx with a multi-platform builder
docker buildx create --use --name multiarch
# Build and push all PostgreSQL community images (UBI9 / EL9)
git clone https://github.com/percona/percona-docker
cd percona-docker/postgresql-containers/community
make all TAG=1.0.0 REGISTRY=myrepo/percona-postgresql-operator
# Or a single image
make postgres17 TAG=1.0.0 REGISTRY=myrepo/percona-postgresql-operator
# UBI8 / EL8 variants
make all-ubi8 TAG=1.0.0-ubi8 REGISTRY=myrepo/percona-postgresql-operator
make all builds all three images (postgres, pgBouncer, pgBackRest) so they stay version-aligned. Override REGISTRY and TAG to point at your own namespace and tagging scheme. Once the images are in your registry, plug them into the CR fields shown earlier and the operator picks them up.
Full build documentation: percona-docker/postgresql-containers/community/README.md.
How to contribute
Community images live in percona/percona-docker, and the build is driven by a transform.py generator that produces the Dockerfiles under build/. The files under build/ are regenerated on every sync, so contributions go through the generator, never through the generated files.
The contribution flow:
- Edit
transform.py(or the package mapping or extension injection it drives). - Run
make test(orpython3 -m pytest tests/ -v). The suite covers package mapping, transforms, extension injection, and pgaudit legacy naming. Version-specific behavior needs new tests intests/test_transform.py. - Regenerate the Dockerfiles with
./sync.sh --force --apply. - Verify locally:
make postgres17 PLATFORMS=linux/amd64 OUTPUT=--load. - Commit
transform.py, the regeneratedbuild/files, and.source-hashestogether so the chain stays consistent.
Adding a new PostgreSQL major follows the same flow plus a TARGETS update in sync.sh and matching variables and targets in the Makefile (for both all and all-ubi8). Then ./sync.sh --apply produces the Dockerfiles.
Full contribution guide: community/CONTRIBUTING.md.
How to provide feedback
Two channels, depending on the shape of the feedback:
-
GitHub issue on percona/percona-postgresql-operator with the
community-imageslabel. Use this for bug reports, missing extensions, build problems, and concrete requests. The label keeps all community-image reports in one filter the team watches. - Community Forum at forums.percona.com. Use this for open-ended discussion, questions, and shape-of-thing feedback ("would this also work for X workload?").
Useful things to include in a GitHub issue: PostgreSQL version, the operator version (3.0.0 or 3.1.0), the image tag you tried, and either the error or the unexpected behavior. If the issue is a build problem, paste the make output. If it is runtime, the operator and database logs help.
Limits worth being honest about
A community image is not a Percona Distribution image. Two practical consequences:
- Distribution-only features will not work. Transparent Data Encryption, for example, lives in the Percona Distribution build. A community image built from upstream PostgreSQL does not include it. If you depend on TDE, run the distribution image.
- Support boundaries are different. Percona Support is responsible for the Percona Distribution images and the operator code. A community image you built yourself is your image. We will help debug operator behavior. We will not own the contents of an image we did not produce.
Ultimately, these are the right trade-offs. The point of community images is to give you transparency and control. Taking on the maintenance of your own image is part of that deal. At the same time, we publish all three images under perconalab/percona-postgresql-operator on Docker Hub so you can evaluate the tech preview without standing up your own build pipeline first. perconalab is Percona's non-production namespace, so use those images for testing; for production, build and sign your own.
What's next
The first step was taking full engineering ownership of Percona Operator for PostgreSQL as an independent project, so the roadmap, the release cadence, and the governance live with one team that the community can talk to directly. Community Docker Images are the next step in that same commitment. If the community adopts this path, we have ideas for what to invest in next.
We will let the community tell us. If this is useful, we keep investing here. We are ready to add more features to the operator around Community Images. Conversely, if nobody adopts it, that is also a signal, and an honest one.
Try the tech preview in 3.0.0. Open an issue if the build flow is rougher than it should be. Tell us what you want next on the forum or directly on GitHub.
Top comments (0)