PostgreSQL with CloudNativePG
PostgreSQL with CloudNativePG
Section titled “PostgreSQL with CloudNativePG”Why an operator instead of a Helm chart?
Section titled “Why an operator instead of a Helm chart?”Helm charts like Bitnami’s PostgreSQL give you a StatefulSet and leave the rest to you — failover, backup scheduling, replica lag monitoring. You write the runbooks, you handle the 2 AM pages.
CloudNativePG (CNPG) is a Kubernetes operator. It watches your PostgreSQL instances, promotes replicas when the primary fails, manages WAL archiving, and exposes metrics. The operator handles the operational work that Helm charts push onto you.
For anything beyond a dev database, use the operator. See Helm vs Kustomize for more on packaging decisions.
Install the operator
Section titled “Install the operator”Add the CNPG Helm repo and install the operator into its own namespace:
helm repo add cnpg https://cloudnative-pg.github.io/chartshelm repo updatehelm install cnpg-operator cnpg/cloudnative-pg \ --namespace cnpg-system \ --create-namespaceWait for the operator deployment to roll out:
kubectl rollout status deployment -n cnpg-system cnpg-cloudnative-pgThe operator installs CRDs for Cluster, Backup, ScheduledBackup, and several others. Once the deployment is ready, you can create PostgreSQL clusters.
Create a PostgreSQL cluster
Section titled “Create a PostgreSQL cluster”This manifest creates one primary and two read replicas (three instances total):
apiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: my-postgres namespace: defaultspec: instances: 3 imageName: ghcr.io/cloudnative-pg/postgresql:17.2-1 bootstrap: initdb: database: app owner: app storage: size: 10Gi walStorage: size: 2Gi resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" enableSuperuserAccess: trueApply it:
kubectl apply -f cluster.yamlWatch the pods come up:
kubectl get pods -l cnpg.io/cluster=my-postgres -wCNPG creates three pods: my-postgres-1 (primary), my-postgres-2, and my-postgres-3 (replicas). The controller elects the primary and configures streaming replication to the replicas.
Services created automatically
Section titled “Services created automatically”CNPG creates three services for every cluster:
| Service | DNS name | Purpose |
|---|---|---|
my-postgres-rw | my-postgres-rw.default.svc | Primary only — read/write traffic |
my-postgres-ro | my-postgres-ro.default.svc | Replicas only — read-only queries |
my-postgres-r | my-postgres-r.default.svc | Any instance — primary or replica |
Point your application at -rw for writes and -ro for reads. The services track the current primary, so after a failover your connection strings stay the same.
Auto-generated secrets
Section titled “Auto-generated secrets”CNPG creates a secret named my-postgres-app containing everything an application needs to connect:
| Key | Value |
|---|---|
username | app |
password | (auto-generated) |
host | my-postgres-rw.default.svc |
port | 5432 |
dbname | app |
uri | Full postgresql:// connection string |
jdbc-uri | JDBC connection string |
pgpass | .pgpass formatted entry |
Mount this secret as environment variables in your application pods. See Node Application for a worked example.
With enableSuperuserAccess: true, CNPG also creates a my-postgres-superuser secret with the postgres superuser credentials.
Connect to the database
Section titled “Connect to the database”Port-forward to the primary service:
kubectl port-forward svc/my-postgres-rw 5432:5432Connect with psql using the auto-generated connection string:
psql "$(kubectl get secret my-postgres-app -o jsonpath='{.data.uri}' | base64 -d)"How failover works
Section titled “How failover works”CNPG monitors every instance through readiness probes and replication status. When the primary fails:
- The controller detects the failure and identifies the most up-to-date replica (lowest replication lag).
- It promotes that replica to primary.
- The
-rwservice updates its endpoints to point at the new primary. - The former primary restarts and rejoins the cluster as a replica.
Applications connected through the -rw service reconnect to the new primary automatically. The failover typically completes in under 30 seconds.
Scheduled backups
Section titled “Scheduled backups”CNPG supports continuous WAL archiving and base backups to object storage. This ScheduledBackup runs a base backup every night at midnight:
apiVersion: postgresql.cnpg.io/v1kind: ScheduledBackupmetadata: name: daily-backupspec: schedule: "0 0 0 * * *" immediate: true backupOwnerReference: self cluster: name: my-postgres method: barmanObjectStoreThis requires a barmanObjectStore section in the Cluster spec that points to an S3-compatible bucket. See the CNPG backup documentation for the full configuration.
Monitoring with Prometheus
Section titled “Monitoring with Prometheus”CNPG exposes metrics on each pod at /metrics. Create a PodMonitor so Prometheus scrapes them:
apiVersion: monitoring.coreos.com/v1kind: PodMonitormetadata: name: my-postgres-monitorspec: selector: matchLabels: cnpg.io/cluster: my-postgres podMetricsEndpoints: - port: metricsThe CNPG project publishes Grafana dashboards that work with these metrics out of the box.
Deploying with Flux CD
Section titled “Deploying with Flux CD”For GitOps, deploy both the operator and the cluster through Flux CD.
First, create a HelmRepository and HelmRelease for the operator:
apiVersion: source.toolkit.fluxcd.io/v1kind: HelmRepositorymetadata: name: cnpg namespace: flux-systemspec: interval: 1h url: https://cloudnative-pg.github.io/charts---apiVersion: helm.toolkit.fluxcd.io/v2kind: HelmReleasemetadata: name: cnpg-operator namespace: cnpg-systemspec: interval: 30m chart: spec: chart: cloudnative-pg version: ">=0.22.0 <1.0.0" sourceRef: kind: HelmRepository name: cnpg namespace: flux-system install: createNamespace: trueThen add the Cluster manifest to a Kustomization that depends on the operator:
apiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: postgres-cluster namespace: flux-systemspec: interval: 30m sourceRef: kind: GitRepository name: flux-system path: ./apps/postgres prune: true dependsOn: - name: cnpg-operatorThe dependsOn field ensures Flux installs the operator (and its CRDs) before attempting to create the Cluster resource.
If you store database credentials in Git, encrypt them with SOPS. See Secrets Management for the setup.