Skip to content

Integrate TimescaleDB with CloudNativePG on Kubernetes

Deploy and operate TimescaleDB on Kubernetes with CloudNativePG for production-ready time-series workloads

Managing PostgreSQL on Kubernetes has come a long way. While deploying a database is relatively straightforward, operating it reliably in production is where the real challenge begins.

Backups, failover, upgrades, monitoring, and disaster recovery all require careful planning. Traditionally, this meant combining Helm charts with custom scripts or manually managing PostgreSQL images as requirements evolved.

CloudNativePG takes a different approach.

Rather than treating PostgreSQL as just another container, CloudNativePG manages it as a Kubernetes-native resource. You declare the desired state of your PostgreSQL cluster, and the operator continuously reconciles the cluster to match it. Tasks such as provisioning, failover, backups, rolling updates, and lifecycle management become automated.

With CloudNativePG's PostgreSQL extension containers, you can also deploy TimescaleDB without maintaining custom PostgreSQL images. PostgreSQL and its extensions are managed independently, making upgrades significantly easier.

This integration guide walks you through deploying TimescaleDB on CloudNativePG, enabling the extension, and creating your first hypertable.

Prerequisites for this integration guide

To follow these steps, you'll need:

  • Kubernetes cluster (v1.31 or later recommended)
  • kubectl configured
  • A default StorageClass
  • Cluster-admin permissions
  • A running Kubernetes node with persistent storage

The deployment architecture is intentionally simple.

+------------------------------------------------+
| Kubernetes Cluster |
+------------------------------------------------+
|
CloudNativePG Operator
|
PostgreSQL Cluster (CNPG)
|
TimescaleDB OSS Extension Container
|
Time-Series Applications & Services

CloudNativePG manages the PostgreSQL cluster lifecycle, while the TimescaleDB extension container provides the required extension binaries. This separation allows PostgreSQL and extensions to evolve independently.

Historically, installing PostgreSQL extensions in Kubernetes often required building and maintaining custom PostgreSQL images.

That approach introduces several operational challenges:

  • Custom image maintenance
  • Rebuilding images for PostgreSQL upgrades
  • Managing extension compatibility
  • Larger CI/CD pipelines
  • More complex image security scanning

CloudNativePG extension containers eliminate these problems. Instead of embedding every extension into the PostgreSQL image, extensions are packaged separately. During cluster initialization, CloudNativePG mounts the extension container into PostgreSQL, making the extension available without modifying the base database image.

This keeps PostgreSQL images small, simplifies upgrades, and makes extension lifecycle management significantly cleaner.

CloudNativePG extension containers rely on the Kubernetes ImageVolume feature. Ensure your Kubernetes cluster supports this feature before continuing.

If you're using a managed Kubernetes service, consult your provider's documentation to verify support.

To confirm the API server has the feature gate enabled, run:

Terminal window
kubectl get pods -n kube-system \
-l component=kube-apiserver \
-o yaml | grep -A 20 "containers:" | grep -E "args:|feature-gates"

You should see output similar to:

--feature-gates=ImageVolume=true

If the feature gate is not enabled, extension containers will not function correctly.

Create a local Kubernetes cluster with Kind

Section titled “Create a local Kubernetes cluster with Kind”

If you don't already have a Kubernetes cluster available, Kind (Kubernetes in Docker) is one of the easiest ways to create a local development environment.

  1. Create a configuration file

    Create a configuration file named kind-config.yaml.

    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    name: ops
    featureGates:
    ImageVolume: true
  2. Create the cluster
    Terminal window
    kind create cluster --config kind-config.yaml
  3. Check the nodes
    Terminal window
    kubectl get nodes

    You should see something similar to:

    NAME STATUS ROLES AGE VERSION
    ops-control-plane Ready control-plane 1m v1.34.x

At the time of writing, CloudNativePG 1.30 is the latest stable release in the 1.30 series.

  1. Install the operator

    Install the operator:

    Terminal window
    kubectl apply --server-side -f \
    https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml
  2. Verify that the operator is running

    Verify that the operator is running:

    Terminal window
    kubectl rollout status deployment -n cnpg-system cnpg-controller-manager

    Once the deployment is successfully rolled out, your Kubernetes cluster is ready to manage PostgreSQL resources.

  1. Create the PostgreSQL cluster
    1. Create a file named cluster-timescaledb.yaml:

      apiVersion: postgresql.cnpg.io/v1
      kind: Cluster
      metadata:
      name: cluster-timescaledb
      spec:
      imageName: ghcr.io/cloudnative-pg/postgresql:18-system-trixie
      instances: 1
      storage:
      size: 1Gi
      postgresql:
      shared_preload_libraries:
      - "timescaledb"
      parameters:
      timescaledb.telemetry_level: 'off'
      max_locks_per_transaction: '128'
      extensions:
      - name: timescaledb-oss
      image:
      reference: ghcr.io/cloudnative-pg/timescaledb-oss:2.27.2-18-trixie

      Key configuration details:

      • 2.27.2 is the TimescaleDB version.
      • 18 represents the PostgreSQL major version.
      • trixie indicates the Debian base image.
    2. Deploy the cluster:

      Terminal window
      kubectl apply -f cluster-timescaledb.yaml

      CloudNativePG provisions the PostgreSQL instance and automatically mounts the TimescaleDB extension container.

  2. Create a database
    1. Create database-timescaledb.yaml:

      apiVersion: postgresql.cnpg.io/v1
      kind: Database
      metadata:
      name: cluster-timescaledb-app
      spec:
      name: app
      owner: app
      cluster:
      name: cluster-timescaledb
      extensions:
      - name: timescaledb
      version: "2.27.2"
    2. Apply it:

      Terminal window
      kubectl apply -f database-timescaledb.yaml

      CloudNativePG creates the database and enables the TimescaleDB extension automatically.

  3. Verify the installation

    After deploying the cluster and database resources, connect to PostgreSQL and verify that the TimescaleDB extension has been installed successfully.

    1. Open a shell inside the PostgreSQL pod:

      Terminal window
      kubectl exec -it cluster-timescaledb-1 -- sh
    2. Launch the PostgreSQL client:

      Terminal window
      psql
    3. Connect to the application database:

      \c app
    4. List installed extensions:

      \dx

      You should see output similar to:

      List of installed extensions
      Name | Version | Schema | Description
      -------------+---------+------------+------------------------------
      plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
      timescaledb | 2.27.2 | public | TimescaleDB OSS

      The TimescaleDB extension is now active and ready for use.

  1. Create a hypertable

    Create a hypertable for sensor readings:

    CREATE TABLE sensor_data (
    time TIMESTAMPTZ NOT NULL,
    sensor_id INTEGER,
    temperature DOUBLE PRECISION,
    humidity DOUBLE PRECISION
    ) WITH (tsdb.hypertable, tsdb.partition_column = 'time');

    Unlike regular PostgreSQL tables, hypertables automatically partition data by time while presenting a single logical table to applications.

  2. Insert sample data

    Populate the table with some test data:

    INSERT INTO sensor_data
    SELECT
    NOW() - (g || ' minutes')::interval,
    (random()*10)::int,
    random()*30,
    random()*100
    FROM generate_series(1,10000) g;
  3. Query time-series data

    Use one of TimescaleDB's most popular functions to calculate the average temperature per hour:

    SELECT
    time_bucket('1 hour', time) AS bucket,
    avg(temperature) AS avg_temperature
    FROM sensor_data
    GROUP BY bucket
    ORDER BY bucket;

    The time_bucket() function groups timestamps into fixed intervals, making time-series aggregations both intuitive and efficient. This is one of the key capabilities that distinguishes TimescaleDB from standard PostgreSQL.

This guide intentionally keeps the deployment simple. For production environments, you'll typically want to enable additional capabilities.

Deploy multiple PostgreSQL instances.

instances: 3

CloudNativePG automatically manages leader election and failover.

CloudNativePG supports several backup strategies:

  • S3-compatible object storage
  • Google Cloud Storage
  • Azure Blob Storage
  • CSI VolumeSnapshots

Choose the approach that aligns with your storage platform and recovery objectives.

CloudNativePG supports restoring your database to an exact point in time using archived WAL files.

This capability is essential for recovering from accidental deletes or application errors.

Production databases should expose metrics to Prometheus and visualize them using Grafana dashboards.

Typical metrics include:

  • Query latency
  • Replication lag
  • Storage usage
  • WAL generation
  • Connection count

Database performance depends heavily on storage.

Use SSD-backed persistent volumes with an appropriate StorageClass, and size volumes based on expected data growth and retention requirements.

One advantage of CloudNativePG extension containers is that PostgreSQL and extension upgrades become independent operations.

Instead of rebuilding custom PostgreSQL images every time a new TimescaleDB release becomes available, you can update the extension container version while allowing CloudNativePG to manage PostgreSQL upgrades according to its lifecycle.

This significantly reduces operational overhead and helps keep both PostgreSQL and TimescaleDB current with minimal effort.

CloudNativePG and TimescaleDB complement each other exceptionally well.

CloudNativePG simplifies PostgreSQL operations through Kubernetes-native automation, while TimescaleDB extends PostgreSQL with powerful time-series capabilities such as hypertables and time-based analytics.

Together they provide a clean, declarative, and production-ready platform for building time-series applications on Kubernetes.

By leveraging CloudNativePG's extension container framework, you can avoid maintaining custom PostgreSQL images, simplify upgrades, and keep your deployment aligned with cloud-native best practices.

Whether you're collecting IoT telemetry, monitoring infrastructure, processing financial events, or analyzing application metrics, this architecture provides a scalable foundation with minimal operational complexity.