Image Volume Extensions
CloudNativePG supports the dynamic loading of PostgreSQL extensions into a
Cluster
at Pod startup using the Kubernetes ImageVolume
feature
and the extension_control_path
GUC introduced in PostgreSQL 18, to which this
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 inside a running pod at a known filesystem path.
You can make the extension available either globally, using the
shared_preload_libraries
option,
or at the database level through the CREATE EXTENSION
command. For the
latter, you can use the Database
resource’s declarative extension management
to ensure consistent, automated extension setup within your PostgreSQL
databases.
Benefits
Image volume extensions decouple the distribution of PostgreSQL operand container images from the distribution of extensions. This eliminates the need to define and embed extensions at build time within your PostgreSQL images—a major adoption blocker for PostgreSQL as a containerized workload, including from a security and supply chain perspective.
As a result, you can:
- Use the official PostgreSQL
minimal
operand images provided by CloudNativePG. - Dynamically add the extensions you need to your
Cluster
definitions, without rebuilding or maintaining custom PostgreSQL images. - Reduce your operational surface by using immutable, minimal, and secure base images while adding only the extensions required for each workload.
Extension images must be built according to the documented specifications.
Requirements
To use image volume extensions with CloudNativePG, you need:
- PostgreSQL 18 or later, with support for
extension_control_path
. - Kubernetes 1.33, with the
ImageVolume
feature gate enabled. - CloudNativePG-compatible extension container images, ensuring:
- Matching PostgreSQL major version of the
Cluster
resource. - Compatible operating system distribution of the
Cluster
resource. - Matching CPU architecture of the
Cluster
resource.
- Matching PostgreSQL major version of the
How it works
Extension images are defined in the .spec.postgresql.extensions
stanza of a
Cluster
resource, which accepts an ordered list of extensions to be added to
the PostgreSQL cluster.
Info
For field-level details, see the
API reference for ExtensionConfiguration
.
Each image volume is mounted at /extensions/<EXTENSION_NAME>
.
By default, CloudNativePG automatically manages the relevant GUCs, setting:
extension_control_path
to/extensions/<EXTENSION_NAME>/share
, allowing PostgreSQL to locate any extension control file within/extensions/<EXTENSION_NAME>/share/extension
dynamic_library_path
to/extensions/<EXTENSION_NAME>/lib
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.
Info
Depending on how your extension container images are built and their layout,
you may need to adjust the default extension_control_path
and
dynamic_library_path
values to match the image structure.
Important
If the extension image includes shared libraries, they must be compiled with the same PostgreSQL major version, operating system distribution, and CPU architecture as the PostgreSQL container image used by your cluster, to ensure compatibility and prevent runtime issues.
How to add a new extension
Adding an extension to a database in CloudNativePG involves a few steps:
- Define the extension image in the
Cluster
resource so that PostgreSQL can discover and load it. - Add the library to
shared_preload_libraries
if the extension requires it. - Declare the extension in the
Database
resource where you want it installed, if the extension supportsCREATE EXTENSION
.
Warning
Avoid making changes to extension images and PostgreSQL configuration
settings (such as shared_preload_libraries
) simultaneously.
First, allow the pod to roll out with the new extension image, then update
the PostgreSQL configuration.
This limitation will be addressed in a future release of CloudNativePG.
For illustration purposes, this guide uses a simple, fictitious extension named
foo
that supports CREATE EXTENSION
.
Adding a new extension to a Cluster
resource
You can add an ImageVolume
-based extension to a Cluster
using the
.spec.postgresql.extensions
stanza. For example:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: foo-18
spec:
# ...
postgresql:
extensions:
- name: foo
image:
reference: # registry path for your extension image
# ...
The name
field is mandatory and must be unique within the cluster, as
it determines the mount path (/extensions/foo
in this example). It must
consist of lowercase alphanumeric characters or hyphens (-
) and must start
and end with an alphanumeric character.
The image
stanza follows the Kubernetes ImageVolume
API.
The reference
must point to a valid container registry path for the extension
image.
Important
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.
Once mounted, CloudNativePG will automatically configure PostgreSQL by appending:
/extensions/foo/share
toextension_control_path
/extensions/foo/lib
todynamic_library_path
This ensures that the PostgreSQL container is ready to serve the foo
extension when requested by a database, as described in the next section. The
CREATE EXTENSION foo
command, triggered automatically during the
reconciliation of the Database
resource,
will work without additional configuration, as PostgreSQL will locate:
- the extension control file at
/extensions/foo/share/extension/foo.control
- the shared library at
/extensions/foo/lib/foo.so
Adding a new extension to a Database
resource
Once the extension is available in the PostgreSQL instance, you can leverage declarative databases to manage the lifecycle of your extensions within the target database.
Continuing with the foo
example, you can request the installation of the
foo
extension in the app
database of the foo-18
cluster using the
following resource definition:
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: foo-app
spec:
name: app
owner: app
cluster:
name: foo-18
extensions:
- name: foo
version: 1.0
CloudNativePG will automatically reconcile this resource, executing the
CREATE EXTENSION foo
command inside the app
database if it is not
already installed, ensuring your desired state is maintained without manual
intervention.
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.
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_PATH
environment variable of the instance manager process, allowing PostgreSQL to locate required system libraries 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:
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
CloudNativePG will configure PostgreSQL with:
/extensions/my-extension/my/share/path
appended toextension_control_path
/extensions/my-extension/my/lib/path
appended 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:
# ...
postgresql:
extensions:
- name: geospatial
extension_control_path:
- postgis/share
- pgrouting/share
dynamic_library_path:
- postgis/lib
- pgrouting/lib
# ...
image:
reference: # registry path for your geospatial image
# ...
# ...
# ...
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:
# ...
postgresql:
extensions:
- name: postgis
# ...
ld_library_path:
- syslib
image:
reference: # registry path for your PostGIS image
# ...
# ...
# ...
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.
Important
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
.
Image Specifications
A standard extension container image for CloudNativePG includes two required directories at its root:
share
: contains the extension control file (e.g.,<EXTENSION>.control
) and any SQL files.lib
: contains the extension's shared library (e.g.,<EXTENSION>.so
) and any additional required libraries.
Following this structure ensures that the extension will be automatically discoverable and usable by PostgreSQL within CloudNativePG without requiring manual configuration.
Important
We encourage PostgreSQL extension developers to publish OCI-compliant extension images following this layout as part of their artifact distribution, making their extensions easily consumable within Kubernetes environments. Ideally, extension images should target a specific operating system distribution and architecture, be tied to a particular PostgreSQL version, and be built using the distribution’s native packaging system (for example, using Debian or RPM packages). This approach ensures consistency, security, and compatibility with the PostgreSQL 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
version
field for the extension in the relevantDatabase
resource 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.