A tenant on a self-hosted Postgres-as-a-service files a ticket: "can you add pgvector to my database?" On a fleet running one monolithic Postgres image, that's not a five-minute favor. Someone edits a Dockerfile, rebuilds, pushes to a registry, bumps the ImageCatalog or imageName reference, and rolls every cluster in the fleet onto the new image — whether or not the other tenants asked for pgvector, PostGIS, or anything else baked into that build. The ticket sits in a sprint, not an hour.
CloudNativePG can now skip that entirely for a specific class of change. PostgreSQL 18's extension_control_path setting plus Kubernetes' ImageVolume feature let a CNPG-managed Cluster mount a PostgreSQL extension — packaged as its own OCI image — as a read-only volume alongside the core Postgres image, instead of compiling it into that image. This piece looks at what actually changes on both sides of that trade for a small platform team: not just "smaller images," but the version gates a team has to clear to use it, and the maintenance burden — real, not hypothetical — of taking on a second class of OCI artifact to publish and version.
The mechanism, and the version gate that decides whether it's usable today
Three separate pieces of software had to land, in order, before this worked:
- PostgreSQL 18 (September 2025) added
extension_control_path, a GUC that tells the server additional directories to search for extension.controlfiles and shared libraries, instead of only the compiled-in$systemlocation. It's superuser-settable and takes effect without a postmaster restart — the server just needs to be told a new directory exists. - Kubernetes'
ImageVolumefeature mounts an OCI-compliant container image as a read-only volume inside a pod. It shipped alpha in 1.31, moved to beta in 1.33 (April 2025) but stayed off by default because containerd and CRI-O hadn't finished their side of it, defaulted on in 1.35 (December 2025) once both runtimes caught up, and graduated to stable in 1.36 (April 2026) — the current release as of this writing. - CloudNativePG wired the two together with a
.spec.postgresql.extensionsstanza on theClusterresource, introduced in 1.27. Version 1.29 added catalog integration (bin_path, anenvfield with an${image_root}placeholder) so extensions can be versioned through the sameImageCatalog/ClusterImageCatalogmechanism as the base image. Version 1.30 (June 29, 2026) added the harder case — in-place major version upgrades (pg_upgrade) for clusters using extension image volumes, mounting the source- and target-version extension images side by side so a failed upgrade reverts cleanly.
The practical gate for a platform team is narrower than "PostgreSQL 18": it's whichever of these is hardest to clear on their own infrastructure.
| Requirement | Minimum | Notes |
|---|---|---|
| PostgreSQL | 18+ | For extension_control_path |
| Kubernetes | 1.35+ for default-on; 1.33–1.34 works with the ImageVolume feature gate flipped manually | 1.36 is stable as of April 2026 |
| Container runtime | containerd v2.1.0+ or CRI-O v1.31+ | Confirm on managed Kubernetes — a cloud provider's stated Kubernetes version doesn't guarantee its managed node image ships a runtime this new |
| CloudNativePG | 1.27+ for the stanza; 1.30+ if in-place major upgrades matter | 1.29+ for ImageCatalog integration |
A team already on a current CNPG and a recent managed Kubernetes clears this without any extra work. A team pinned to Kubernetes 1.33 or 1.34 for stability reasons has one explicit decision to make first — flip a feature gate — before any of the rest of this applies.
The concrete before/after: what actually gets smaller, and by how much
CloudNativePG's own worked example (the angus cluster in its extensions recipe) uses a minimal Postgres 18 base image at 260MB and mounts a pgvector:0.8.1-18-trixie extension image at 613KB. Adding pgvector to that cluster means the base stays at 260MB and the pod gains a 613KB read-only mount — not a new 260MB+ image.
That framing is honest only if it holds up against a real single-purpose comparison, so here's the less flattering number: the official pgvector/pgvector:0.8.6-pg18-trixie image — Postgres and pgvector compiled into one image, the traditional way — is 156.4MB. For exactly one extension, a purpose-built monolithic image can beat the split base-plus-mount total. The ImageVolume approach isn't a universal size win at n=1.
Where it wins is as the extension count grows, because a monolithic image has to bake in the union of everything any tenant might ask for, shipped to every tenant regardless of use. The real-world example of what that looks like: timescale/timescaledb-ha:pg16.4-ts2.16.1-all-oss — TimescaleDB plus a bundled set of extensions including PostGIS and pgvector, built to cover a broad set of tenant needs in one image — runs 4.05GB. That total includes orchestration tooling beyond just the extensions, so it's not a clean per-extension number, but it's a real, shipped example of what "cover everything preemptively" costs in practice, not a hypothetical.
A CNPG base fixed at 260MB, with only the specific extensions a given tenant actually uses mounted on top, doesn't scale toward that number as the catalog of available extensions grows — only as what any single tenant uses grows.
Mechanically, adding pgvector for one tenant is a patch to that tenant's Cluster spec adding an entry under postgresql.extensions, referencing the image and, where needed, ld_library_path for extensions like PostGIS with more complex linking. CNPG picks that up and does a rolling restart of the pod.
That's the honest mechanical claim: this is not a rebuild-push-roll-the-fleet cycle reduced to an API call, and it's also not zero-disruption — it's a targeted rolling restart of the one cluster, not the fleet, and not a hot in-place add without any pod recreation. Adding or removing an extension entry, changing its ld_library_path, or updating environment variables all trigger that restart. Some of those — non-extension-list changes like ld_library_path or env var edits — currently require a manual restart rather than an automatic one, so a team automating this end-to-end needs to account for that gap explicitly rather than assume the operator always reconciles it for them.
The added moving parts, honestly accounted
This is the part the appeal of "smaller images" tends to skip past: adopting extension-as-ImageVolume means a platform team is now consuming — and in some cases publishing — a second category of OCI artifact, distinct from the base Postgres image, with its own versioning and trust questions.
The build side is often free. CloudNativePG's community maintains postgres-extensions-containers, and as of this writing it covers exactly seven extensions: pgAudit, pg_crash, pg_ivm, pgvector, PostGIS, TimescaleDB (Apache-2 edition), and wal2json. A platform team whose tenant requests land inside that list — which, in practice, likely covers the majority of real-world asks (pgvector for AI workloads, PostGIS for geo) — pays zero build or publish cost. The Cluster spec just references ghcr.io/cloudnative-pg/pgvector:<tag>.
Only a team that needs an extension outside that list of seven has to build and publish their own. The repo does provide a template and a Dagger/Task-based build process plus a documented contribution path, which lowers that bar but doesn't remove it — someone still owns a Dockerfile, a build pipeline, and a registry namespace that didn't exist before.
The ongoing side isn't free even for the seven catalog extensions, and this is the part worth budgeting for regardless of which extensions a team needs:
- Pinning discipline. These images are built roughly weekly and tagged per Postgres version and Debian base (
trixie/bookworm). AClusterspec referencing a moving tag rather than a specific digest inherits whatever changed in that week's rebuild — the same supply-chain hygiene question that already applies to the base image, now duplicated across every extension a tenant uses. - Base-image coupling. An extension image built against
18-trixiehas to be revisited when the base Postgres image's OS or major version moves — that's a compatibility check a team now tracks per extension, not once per fleet. - Trust boundary. CloudNativePG's core operand images are signed with cosign, carry SLSA provenance, and ship an SPDX SBOM. Before mounting a
postgres-extensions-containersimage directly into a tenant's running database pod, verify that specific image carries the same signing and provenance guarantees as the core operand images — don't assume it inherits them by association. That's a five-minute check (cosign verifyagainst the actual image reference), but it's a check a team wasn't doing before, because there was no second image to check.
None of this is a reason to avoid the feature. It's the accounting the size numbers alone don't show: the cost moved from "rebuild and roll the fleet" to "pin, track, and verify a growing set of smaller artifacts" — a real shift in kind of work, not a reduction to zero.
What this concretely changes for a tenant database offering
For a self-hosted PaaS running CNPG for tenant Postgres, three things are now true that weren't before CNPG 1.27:
- Per-tenant extension enablement no longer requires a fleet-wide image bump. One tenant getting pgvector doesn't touch any other tenant's cluster or image.
- Extension upgrade cadence decouples from core Postgres upgrade cadence. Bumping
pgvectorto a new version is a per-cluster image reference change, not bundled into the next base-image rebuild — and as of CNPG 1.30, that decoupling survives a PostgreSQL major-version upgrade too, since the operator mounts old and new extension images side by side during an in-placepg_upgrade. - The "which extensions do we support" question shrinks to a version-gate checklist, not an image-engineering project — provided the target extension is one of the seven the community already publishes, and the cluster clears the Kubernetes/CNPG version floor above.
What it doesn't change: a platform team still needs a versioning and trust policy for a second class of artifact, and "add an extension" is still a rolling restart of that tenant's cluster, not a hot, zero-disruption change. For a small team weighing whether to offer this today, the honest read is: adopt it for the common cases (pgvector, PostGIS) where the build cost is already zero, budget real time for pinning and provenance discipline on an ongoing basis, and treat "clear the Kubernetes 1.35 floor" as the actual blocking task rather than "wait for PostgreSQL 18," which most teams have already cleared.
bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. bex doesn't manage the tenant database itself; an operator's own CNPG install runs next to their bex-deployed App, which means this exact version-gate-and-provenance checklist is one every bex operator running CNPG for tenant Postgres will hit directly. Star the repo on GitHub or deploy your first app today.



