Image Volume Extensions
CloudNativePG supports the dynamic loading of PostgreSQL extensions into a
Cluster at Pod startup using the Kubernetes ImageVolume feature
alongside the extension_control_path GUC introduced in PostgreSQL 18, a
feature to which the CloudNativePG project contributed.
This feature allows you to mount a PostgreSQL extension, packaged as an OCI-compliant container image, as a read-only and immutable volume at a designated filesystem path within a running pod.
For extensions requiring database-level installation via the CREATE EXTENSION
command, you can use the Database resource’s declarative extension management
to ensure consistent, automated setup across all your PostgreSQL databases.
Official Extension Images and Catalogs
The CloudNativePG Community maintains a suite of extension container
images, including
pgvector
and
PostGIS,
as part of the postgres-extensions-containers project).
These images are built on top of the
official PostgreSQL minimal images.
While this documentation provides the necessary technical specifications for third parties to build their own images and catalogs, the following instructions focus specifically on the deployment and usage of our official extension images and catalogs.
Benefits
By decoupling the distribution of extensions from the PostgreSQL operand
images, this feature removes a significant barrier to running PostgreSQL in
containers. It eliminates the need to embed extensions at build time, allowing
you to use official minimal operand images and dynamically add only the
required extensions to your Cluster definitions—either directly or via an
image catalog.
This approach significantly reduces the attack surface of your database clusters by ensuring that the core database container contains only the essential binaries required for operation. By excluding unnecessary extensions, libraries, and build-time dependencies, you minimize potential entry points for exploits and simplify vulnerability management. This architecture enhances supply chain security and reduces operational overhead by maintaining an immutable, minimal base image for your data workloads.
Extension images must be built according to the documented specifications.
Requirements
To use image volume extensions with CloudNativePG, you need:
- PostgreSQL 18 or later: Required for
extension_control_pathsupport. - Kubernetes 1.35 or later: The
ImageVolumefeature is enabled by default. Users on Kubernetes 1.33 and 1.34 must manually enable theImageVolumefeature gate. - Container runtime with
ImageVolumesupport:containerdv2.1.0 or later, orCRI-Ov1.31 or later.
- CloudNativePG-compatible extension container images, ensuring:
- Matching PostgreSQL major version of the
Clusterresource. - Compatible operating system distribution of the
Clusterresource. - Matching CPU architecture of the
Clusterresource.
- Matching PostgreSQL major version of the
How it works
An extension image can be added to a new or existing Cluster resource using
the .spec.postgresql.extensions stanza.
When a new extension is added to a running Cluster, CloudNativePG will
automatically trigger a rolling update to attach the new
image volume to each pod. Before adding a new extension in production,
ensure you have thoroughly tested it in a staging environment to prevent
configuration issues that could leave your PostgreSQL cluster in an unhealthy
state.
For field-level details, see the
API reference for ExtensionConfiguration.
Configuration Source and Precedence
The extensions stanza accepts a list of entries, each requiring a name that
must be unique within the cluster.
The name must consist of lowercase alphanumeric characters, underscores (_)
or hyphens (-) and must start and end with an alphanumeric character.
Each entry defines the configuration for a container image and specifies the options PostgreSQL needs to locate and load the extension:
-
Via Image Catalog: If the cluster references an
ImageCatalogthat defines extensions for the current PostgreSQL major version, those definitions serve as the default configuration. This allows the catalog to centrally manage theimage.reference, while the cluster simply enables the extension by name. -
Direct Definition: If a cluster does not use an image catalog, the
image.referencefield is mandatory and must explicitly point to a valid container registry path for the extension image. Theimagestanza follows the KubernetesImageVolumeAPI.
Following the "convention over configuration" paradigm, CloudNativePG
provides total flexibility: any value inherited from a catalog, including the
image.reference, can be selectively overridden within the Cluster
definition to meet specific local requirements.
Mounting and PostgreSQL Configuration
Each extension is mounted as a read-only volume at
/extensions/<EXTENSION_NAME> inside the pod.
By default, CloudNativePG automatically manages the relevant GUCs by setting:
extension_control_pathto/extensions/<EXTENSION_NAME>/share, allowing PostgreSQL to locate any extension control file within/extensions/<EXTENSION_NAME>/share/extensiondynamic_library_pathto/extensions/<EXTENSION_NAME>/lib
Extension names containing underscores (e.g., pg_ivm) are converted to use
hyphens (e.g., pg-ivm) for Kubernetes volume names to comply with RFC 1123
DNS label requirements. Do not use extension names that become identical after
sanitization (e.g., pg_ivm and pg-ivm both sanitize to pg-ivm). The
webhook validation will prevent such conflicts.
These values are appended in the order in which the extensions are defined in
the extensions list, ensuring deterministic path resolution within
PostgreSQL. This allows PostgreSQL to discover and load the extension without
requiring manual configuration inside the pod.
While you can manually adjust these paths to match a custom image layout, official CloudNativePG catalogs pre-configure these options to the correct values for each extension, ensuring they work out-of-the-box.
If an extension image includes shared libraries, they must be compiled for the same PostgreSQL major version, operating system distribution, and CPU architecture as the operand image. Using official CloudNativePG catalogs ensures this compatibility automatically: the catalogs are designed to match the specific environment of the cluster, preventing runtime issues caused by library or architecture mismatches.
Installation in a Database
You can leverage declarative database management
to automate the final activation of any extension that supports the standard
PostgreSQL CREATE EXTENSION mechanism.
Once the extension image is mounted and the GUCs are configured, the extension
is available to the PostgreSQL instance but is not yet active within a specific
database. To install it, define a Database resource. The example below uses
pgvector, following the implementation standards of the
official postgres-extensions-containers project:
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: cluster-example-app
spec:
name: app
owner: app
cluster:
name: cluster-example
extensions:
- name: vector
version: "<VERSION>"
CloudNativePG automatically reconciles this resource by executing
CREATE EXTENSION IF NOT EXISTS vector within the target database. This
ensures your desired state is maintained and consistently applied across all
instances.
Some PostgreSQL components, often referred to as modules, do not use the
CREATE EXTENSION mechanism. These typically consist of shared libraries that
must be loaded via shared_preload_libraries at server start.
Even when using an image catalog to simplify the distribution of these
binaries, you must still manually add the library name to the
shared_preload_libraries option
in your Cluster definition to ensure it is loaded into memory.
Adding an Extension to a Postgres Cluster
As anticipated earlier, CloudNativePG offers two ways to define extension images. While both achieve the same result at the Pod level, using an image catalog is the recommended approach for maintaining a consistent, production-ready supply chain.
Via an Image Catalog (Recommended)
Support for extension container images in image catalogs was introduced in CloudNativePG 1.29.
When you use an image catalog that covers extensions
like the official ones
provided by the community, the complex configuration details, such as the
specific container image reference and the required filesystem paths, are
managed centrally.
Your Cluster definition remains clean and declarative, as it only needs to
"opt-in" to the extension by name. The operator automatically handles the
resolution of the image and the required PostgreSQL settings based on the
catalog's definitions.
To enable an extension like pgvector from a catalog, add it to the
extensions list in your cluster like in the following excerpt:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
spec:
# ... <snip>
imageCatalogRef:
apiGroup: postgresql.cnpg.io
kind: ClusterImageCatalog
name: postgresql-minimal-trixie
major: 18
postgresql:
extensions:
- name: pgvector # Resolves all details, including the image reference, from the catalog
# ... <snip>
This method ensures that the extension image is always compatible with the PostgreSQL operand image defined in the same catalog entry.
Directly in the Cluster
Defining extensions directly in the Cluster resource is the original method
and remains the only option for versions prior to CloudNativePG 1.29.
It is also useful if you need to use an extension not present in your current
catalog or for testing custom images.
You can define an extension directly within the Cluster resource without a
catalog. In this case, you must explicitly provide the image reference.
The following example shows how to add pgvector by explicitly pointing to its
official container image:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
spec:
# ... <snip>
postgresql:
extensions:
- name: pgvector
image:
reference: ghcr.io/cloudnative-pg/pgvector:<TAG>
Remember that configuration provided directly in the Cluster takes
precedence. If you reference a catalog but also define the same extension name
in the Cluster stanza, the settings in the Cluster will override those in
the catalog.
While every field in an ExtensionConfiguration
can be overridden at the Cluster level to provide total flexibility, the
name field is the exception.
The name serves as the unique identifier; changing it will define a new
extension entry rather than overriding an existing one from a catalog.
Verifying Extension Status
Because extensions can be sourced from both image catalogs and direct cluster
definitions, CloudNativePG provides a "resolved" view of the configuration in
the Cluster status. This is the final, effective state the operator uses to
provision the pods.
You can inspect the status.pgDataImageInfo.extensions field to see the
results of any inheritance or overrides:
# ... <snip>
status:
# ... <snip>
pgDataImageInfo:
image: # registry path for your PostgreSQL image
majorVersion: 18
extensions:
- name: foo
image:
reference: # registry path for your `foo` extension image
- name: bar
image:
reference: # registry path for your `bar` extension image
# ... <snip>
This section is particularly useful for:
- Validating Resolution: Confirming that an extension requested by name in
the
Clusterhas correctly pulled itsimage.referencefrom the associated catalog. - Confirming Overrides: Verifying that a cluster-level override (such as a specific image version) has successfully replaced the catalog's default value.
- Troubleshooting: Ensuring all desired extensions are recognized by the operator before the pods undergo a rolling update.
Removing an Extension from a PostgreSQL Cluster
Removing an extension involves a two-step process to clean up both the infrastructure and the database metadata.
You should first remove the extension from the database to avoid "library not
found" errors. If you are using declarative management, update your Database
resource by setting ensure: absent for the extension:
spec:
# ... <snip>
extensions:
- name: pgvector
ensure: absent
# ... <snip>
This triggers CloudNativePG to execute DROP EXTENSION within the database.
If the extension was added to shared_preload_libraries, you must also remove
it from your Cluster configuration.
Then, remove the extension entry from the .spec.postgresql.extensions list in
your Cluster resource. The operator will perform a rolling update to detach
the ImageVolume and update the relevant GUC paths.
Advanced Topics
In some cases, the default expected structure may be insufficient for your extension image, particularly when:
- The extension requires additional system libraries.
- Multiple extensions are bundled in the same image.
- The image uses a custom directory structure.
- The extension requires external binaries to function properly.
Following the "convention over configuration" paradigm, CloudNativePG allows you to finely control the configuration of each extension image through the following fields:
extension_control_path: A list of relative paths within the container image to be appended to PostgreSQL’sextension_control_path, allowing it to locate extension control files.dynamic_library_path: A list of relative paths within the container image to be appended to PostgreSQL’sdynamic_library_path, enabling it to locate shared library files for extensions.ld_library_path: A list of relative paths within the container image to be appended to theLD_LIBRARY_PATHenvironment variable of the Postgres process, allowing PostgreSQL to locate required system libraries at runtime.bin_path: A list of relative paths within the container image to be appended to thePATHenvironment variable of the Postgres process, allowing PostgreSQL to locate binaries at runtime.
This flexibility enables you to support complex or non-standard extension images while maintaining clarity and predictability.
Setting Custom Paths
If your extension image does not use the default lib and share directories
for its libraries and control files, you can override the defaults by
explicitly setting extension_control_path and dynamic_library_path.
For example:
spec:
# ... <snip>
postgresql:
extensions:
- name: my-extension
extension_control_path:
- my/share/path
dynamic_library_path:
- my/lib/path
image:
reference: # registry path for your extension image
# ... <snip>
# ... <snip>
# ... <snip>
CloudNativePG will configure PostgreSQL with:
/extensions/my-extension/my/share/pathappended toextension_control_path/extensions/my-extension/my/lib/pathappended todynamic_library_path
This allows PostgreSQL to discover your extension’s control files and shared libraries correctly, even with a non-standard layout.
Multi-extension Images
You may need to include multiple extensions within the same container image, adopting a structure where each extension’s files reside in their own subdirectory.
For example, to package PostGIS and pgRouting together in a single image, each in its own subdirectory:
# ...
spec:
# ... <snip>
postgresql:
extensions:
- name: geospatial
extension_control_path:
- postgis/share
- pgrouting/share
dynamic_library_path:
- postgis/lib
- pgrouting/lib
# ... <snip>
image:
reference: # registry path for your geospatial image
# ... <snip>
# ... <snip>
# ... <snip>
Including System Libraries
Some extensions, such as PostGIS, require system libraries that may not be
present in the base PostgreSQL image. To support these requirements, you can
package the necessary libraries within your extension container image and make
them available to PostgreSQL using the ld_library_path field.
For example, if your extension image includes a system directory with the
required libraries:
# ...
spec:
# ... <snip>
postgresql:
extensions:
- name: postgis
# ... <snip>
ld_library_path:
- system
image:
reference: # registry path for your PostGIS image
# ... <snip>
# ... <snip>
# ... <snip>
CloudNativePG will set the LD_LIBRARY_PATH environment variable to include
/extensions/postgis/system, allowing PostgreSQL to locate and load these
system libraries at runtime.
Since ld_library_path must be set when the PostgreSQL process starts,
changing this value requires a cluster restart for the new value to take
effect.
CloudNativePG does not currently trigger this restart automatically; you will
need to manually restart the cluster (e.g., using cnpg restart) after
modifying ld_library_path.
Including external binaries
Some extensions might need to provide some binaries required to interact with
the extension itself. To support these requirements, you can
package the necessary binaries within your extension container image and make
them available to PostgreSQL using the bin_path field.
For example, if your extension image includes a bin directory with the
required binaries:
# ...
spec:
# ... <snip>
postgresql:
extensions:
- name: my-extension
# ... <snip>
bin_path:
- bin
image:
reference: # registry path for your extension image
# ... <snip>
# ... <snip>
# ... <snip>
CloudNativePG will append /extensions/my-extension/bin to the PATH environment
variable of the Postgres process, allowing PostgreSQL to locate these
binaries at runtime.
Since bin_path must be set when the PostgreSQL process starts,
changing this value requires a cluster restart for the new value to take
effect.
CloudNativePG does not currently trigger this restart automatically; you will
need to manually restart the cluster (e.g., using cnpg restart) after
modifying bin_path.
Image Specifications
A standard extension container image for CloudNativePG includes two required directories at its root:
/share/: contains anextensionsubdirectory with the extension control file (e.g.<EXTENSION>.control) and the corresponding SQL files./lib/: contains the extension’s shared library (e.g.<EXTENSION>.so) as well as any other required libraries.
Following this structure ensures that the extension will be automatically discoverable and usable by PostgreSQL within CloudNativePG without requiring manual configuration.
We encourage PostgreSQL extension developers and third-party providers to
publish OCI-compliant extension images following this layout.
For practical implementation details, we recommend reviewing the
postgres-extensions-containers project,
which serves as the reference for building official CloudNativePG extension images.
Ideally, extension images should:
- Target a specific operating system distribution and set of CPU architectures.
- Be tied to a particular PostgreSQL major version.
- Be built using the distribution’s native packaging system (e.g.,
.debor.rpmpackages) to ensure consistency, security, and compatibility with the PostgreSQL operand images used in your clusters.
Caveats
Currently, adding, removing, or updating an extension image triggers a restart of the PostgreSQL pods. This behavior is inherited from how image volumes work in Kubernetes.
Before performing an extension update, ensure you have:
- Thoroughly tested the update process in a staging environment.
- Verified that the extension image contains the required upgrade path between the currently installed version and the target version.
- Updated the
versionfield for the extension in the relevantDatabaseresource definition to align with the new version in the image.
These steps help prevent downtime or data inconsistencies in your PostgreSQL clusters during extension updates.