Dynamic extension loading in CloudNativePG

PostgreSQL's extensibility has long come at an operational cost on Kubernetes: CNPG clusters that needed extensions required custom container images built with those extensions included. Two recent platform changes remove that requirement, letting extensions be mounted into a cluster at pod startup while the base PostgreSQL image stays as shipped.

The first piece is PostgreSQL 18's extension_control_path parameter, a search path for extensions. The second is Kubernetes 1.33's ImageVolume feature, which mounts an OCI-compliant container image as an immutable, read-only volume at a chosen filesystem path. Because extensions are no longer baked in at image build time, operators can stay on official images, add extensions without rebuilding, and decouple extension distribution from the container image itself.

Requirements

  • PostgreSQL 18+
  • Kubernetes 1.33+
  • A container runtime with ImageVolume support: containerd v2.1.0+ or CRI-O v1.31+
  • CloudNativePG-compatible extension container images

Bootstrapping with pgvector

Starting from a manifest that declares the cluster, apply it and then verify the extension in the app database.

Adding PostGIS to a running cluster

Extensions can also be installed after bootstrapping by editing the manifest and reapplying it, then checking the extensions present in the app database.

Library path adjustment

PostGIS places its shared libraries in the /system directory, so LD_LIBRARY_PATH must point there. On an actual cluster the variable resolves to /extensions/postgis/system rather than /system. That is a consequence of how CNPG mounts the extension image: images land under /extensions/$EXTENSION_NAME/$DIRECTORY_PATH_IN_EXTENSION_CONTAINER_IMAGE, which for PostGIS yields /extensions/postgis/system.

Impact

With extension_control_path and ImageVolume in place, CloudNativePG can load extensions dynamically at pod startup, retiring the practice of building extensions into PostgreSQL container images. As the pgvector and PostGIS cases show, extensions can be added at cluster creation or introduced later through manifest updates when the requirements are met, while the base image remains minimal, immutable and officially maintained. The separation reduces image sprawl and draws a clear line between the PostgreSQL runtime and extension distribution.