Image Volume Extensions
=======================
.. raw:: html
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.
If the extension requires one or more shared libraries to be pre-loaded
at server start, you can add them via the :ref:`shared_preload_libraries ` . Also, if your
extension requires being created at the database level through the
``CREATE EXTENSION`` command, you can use the :ref:`Configure the application database `
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 :ref:`Image 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.
- **Container runtime with ``ImageVolume`` support**:
- ``containerd`` v2.1.0 or later, or
- ``CRI-O`` v1.31 or later.
- **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.
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.
.. Note::
For field-level details, see the :ref:`API reference for `ExtensionConfiguration` ` .
Each image volume is mounted at ``/extensions/`` .
By default, CloudNativePG automatically manages the relevant GUCs,
setting:
- ``extension_control_path`` to ``/extensions//share`` ,
allowing PostgreSQL to locate any extension control file within
``/extensions//share/extension``
- ``dynamic_library_path`` to ``/extensions//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.
.. Note::
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.
.. Note::
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:
1. Define the extension image in the ``Cluster`` resource so that
PostgreSQL can discover and load it.
2. Add the library to :ref:`shared_preload_libraries `
if the extension requires it.
3. Declare the extension in the ``Database`` resource where you want it
installed, if the extension supports ``CREATE 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:
.. code:: yaml
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.
.. Note::
When a new extension is added to a running `Cluster` , CloudNativePG will automatically trigger a :ref:`Rolling updates ` 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`` to ``extension_control_path``
- ``/extensions/foo/lib`` to ``dynamic_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 :ref:`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 :ref:`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:
.. code:: yaml
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’s
``extension_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’s
``dynamic_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 the ``LD_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:
.. code:: yaml
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 to
``extension_control_path``
- ``/extensions/my-extension/my/lib/path`` appended to
``dynamic_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:
.. code:: yaml
## ...
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:
.. code:: yaml
## ...
spec:
# ...
postgresql:
extensions:
- name: postgis
# ...
ld_library_path:
- system
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.
.. Note::
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 an ``extension`` subdirectory with the
extension control file (e.g. ``.control`` ) and the
corresponding SQL files.
- ``/lib/`` : contains the extension’s shared library
(e.g. ``.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.
.. Note::
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 relevant
``Database`` 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.