In the evolving landscape of database management on Kubernetes, a significant shift is occurring in how PostgreSQL extensions are deployed and maintained. CloudNativePG, a leading operator for PostgreSQL on Kubernetes, has unveiled an innovative approach that leverages the Kubernetes ImageVolume feature to mount extensions like pgvector and PostGIS from separate, dedicated images. This declarative method completely decouples the PostgreSQL core from extension binaries, marking a pivotal moment in database infrastructure management.

"This new flexibility is unlocked by the latest crucial steps: the combination of PostgreSQL 18's extension_control_path GUC and the Kubernetes ImageVolume feature."

The Old Way: Monolithic Custom Images

Traditionally, achieving immutability in PostgreSQL extensions on Kubernetes required building large, custom container images with every extension pre-packaged. For extensions like pgvector, the common approach was to use standard CNPG images that came with only a few extensions pre-installed. This method, while functional, created several challenges:

  • Large image sizes with unnecessary dependencies
  • Complex build processes for each extension combination
  • Difficulty in updating individual extensions without rebuilding the entire image
  • Inflexibility when trying to evaluate new extensions
Article illustration 1

The New Approach: Decoupled Extension Images

The new approach represents a fundamental shift in how extensions are managed. By utilizing PostgreSQL 18's extension_control_path Grand Unified Configuration (GUC) variable alongside Kubernetes' ImageVolume feature, CloudNativePG can mount extension binaries from separate container images directly into PostgreSQL pods.

"This allows us to use the small, official minimal PostgreSQL images while seamlessly integrating complex extensions like pgvector and PostGIS," explains the approach's architect.

Technical Requirements

Before diving into implementation, it's crucial to understand the technical prerequisites:

  • PostgreSQL 18: The new extension_control_path GUC is essential for this approach
  • Kubernetes 1.33+: The ImageVolume feature, which is not yet enabled by default, is required
  • CloudNativePG: The latest stable version must be installed

For local development using Kind, the ImageVolume feature gate must be explicitly enabled:

(cat << EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  ImageVolume: true
EOF
) | kind create cluster --config -

Implementation: Starting with a Minimal Cluster

The process begins with creating a basic CloudNativePG cluster using the official minimal image for PostgreSQL 18:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: angus
spec:
  # the small, official `minimal` CNPG base image for Postgres 18
  imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
  instances: 1

  storage:
    size: 1Gi

This minimal image, at just 260MB, contains only the core database binaries, providing a clean foundation for the extension strategy.

Adding pgvector via a Dedicated Image

The magic happens in the postgresql.extensions section of the Cluster specification. Here, we instruct CloudNativePG to mount a separate image containing the pgvector binaries:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: angus
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
  instances: 1

  storage:
    size: 1Gi

  postgresql:
    extensions:
    - name: pgvector
      image:
        reference: ghcr.io/cloudnative-pg/pgvector:0.8.1-18-trixie

The pgvector extension image weighs only 613KB, demonstrating the efficiency of this approach.

Declarative Extension Activation

The final step involves creating a Database resource that declaratively activates the extension:

apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
  name: angus-app
spec:
  name: app
  owner: app
  cluster:
    name: angus
  extensions:
  - name: vector
    version: '0.8.1'

CloudNativePG handles both mounting the binary files via ImageVolume and running the necessary CREATE EXTENSION SQL commands.

Verification

Once the manifests are applied, verification is straightforward:

# Check mounted extensions
kubectl exec -ti angus-1 -c postgres -- ls /extensions/
# Expected output: pgvector

# Verify active extensions
kubectl cnpg psql angus -- app -c '\dx'
# Expected output includes:
# vector | 0.8.1 | 0.8.1 | public | vector data type and ivfflat and hnsw access methods

Scaling Up: Managing Complex Extensions

The true power of this approach becomes apparent when managing complex extensions like PostGIS, which requires multiple companion extensions and library dependencies. The solution elegantly handles this complexity:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: angus
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
  instances: 1

  storage:
    size: 1Gi

  postgresql:
    extensions:
    - name: pgvector
      image:
        reference: ghcr.io/cloudnative-pg/pgvector:0.8.1-18-trixie
    - name: postgis
      image:
        reference: ghcr.io/cloudnative-pg/postgis-extension:3.6.1-18-trixie
      ld_library_path:
      - system

And the corresponding Database resource:

apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
  name: angus-app
spec:
  name: app
  owner: app
  cluster:
    name: angus
  extensions:
  - name: vector
    version: '0.8.1'
  - name: postgis
    version: '3.6.1'
  - name: postgis_raster
  - name: postgis_sfcgal
  - name: fuzzystrmatch
  - name: address_standardizer
  - name: address_standardizer_data_us
  - name: postgis_tiger_geocoder
  - name: postgis_topology

Benefits and Implications

This approach offers several significant advantages:

  1. Decoupling: The core PostgreSQL image can be upgraded independently of extension images
  2. Dynamic Evaluation: Extensions can be added to existing clusters without rebuilding
  3. Small Images: Base images remain minimal, secure, and simple
  4. Consistency: CloudNativePG handles complex volume mounting and dependency mapping
  5. Immutability: Achieves immutability without the custom image maintenance overhead

"The ability to mount extensions from separate images using ImageVolume with CloudNativePG 1.27+ is a game-changer," states the project maintainer. "It allows us to decouple, dynamically evaluate extensions, maintain small images, and ensure consistency across the entire cluster."

Community and Future Directions

The CloudNativePG community is actively working to standardize extension image creation through the postgres-extensions-containers repository on GitHub. The goal is to scale the number of supported extensions by providing a framework that contributors can use to add extensions they maintain.

"The goal of this project is to scale up the number of supported extensions by providing a framework that can be used by more contributors to add extensions they like, as long as they become component owners/maintainers for that extension in the CloudNativePG community."

As Kubernetes continues to evolve and PostgreSQL 18 becomes more widely adopted, this approach to extension management is poised to become the standard for database deployments on containerized platforms. The combination of declarative configuration, immutability, and dynamic flexibility represents a significant advancement in database operations on Kubernetes.

Source: https://www.gabrielebartolini.it/articles/2025/12/cnpg-recipe-23-managing-extensions-with-imagevolume-in-cloudnativepg/