Why CloudNativePG?
CloudNativePG is the most Kubernetes-native PostgreSQL operator. Unlike others that bolt Patroni on top, CNPG was built from scratch for Kubernetes:
- No external dependencies β no etcd, no Consul, no Patroni
- Declarative API β single
ClusterCRD manages everything - WAL archiving β continuous backup to S3/GCS/Azure Blob
- Automated failover β promotion in seconds, no split-brain
- Connection pooling β built-in PgBouncer integration
- Fencing and maintenance β safe node drains
- CNPG Plugin for kubectl β native CLI experience
Installation
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm install cnpg cnpg/cloudnative-pg \
--namespace cnpg-system \
--create-namespaceVerify:
kubectl get pods -n cnpg-system
# cnpg-cloudnative-pg-xxx Running
# Install kubectl plugin
curl -sSfL https://github.com/cloudnative-pg/cloudnative-pg/raw/main/hack/install-cnpg-plugin.sh | shDeploy a PostgreSQL Cluster
Minimal 3-Node HA Cluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
storage:
size: 50Gi
storageClass: gp3
postgresql:
parameters:
shared_buffers: "256MB"
effective_cache_size: "768MB"
work_mem: "8MB"
maintenance_work_mem: "128MB"
max_connections: "200"
max_wal_size: "1GB"
bootstrap:
initdb:
database: myapp
owner: myapp
encoding: UTF8
localeCType: en_US.utf8
localeCollate: en_US.utf8
superuserSecret:
name: postgres-superuser
monitoring:
enablePodMonitor: trueCreate the superuser secret:
kubectl create secret generic postgres-superuser \
--from-literal=username=postgres \
--from-literal=password=$(openssl rand -base64 24)Check Status
kubectl cnpg status my-postgres
# Cluster Summary:
# Name: my-postgres
# Instances: 3
# Ready Instances: 3
# Primary: my-postgres-1
# Status: Cluster in healthy stateContinuous Backup to S3
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres
spec:
instances: 3
storage:
size: 50Gi
backup:
barmanObjectStore:
destinationPath: s3://my-backups/postgres/
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
maxParallel: 4
data:
compression: gzip
retentionPolicy: "30d"
---
# Schedule regular base backups
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *"
cluster:
name: my-postgres
backupOwnerReference: self
immediate: truePoint-in-Time Recovery
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres-recovered
spec:
instances: 3
storage:
size: 50Gi
bootstrap:
recovery:
source: my-postgres
recoveryTarget:
targetTime: "2026-06-01T14:30:00Z"
externalClusters:
- name: my-postgres
barmanObjectStore:
destinationPath: s3://my-backups/postgres/
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEYConnection Pooling with PgBouncer
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: my-postgres-pooler
spec:
cluster:
name: my-postgres
instances: 2
type: rw # or "ro" for read replicas
pgbouncer:
poolMode: transaction
parameters:
max_client_conn: "1000"
default_pool_size: "25"
min_pool_size: "5"Applications connect to the Pooler service instead of the Cluster directly:
# Read-write (via pooler)
psql postgresql://myapp:pass@my-postgres-pooler:5432/myapp
# Read-only (direct to replicas)
psql postgresql://myapp:pass@my-postgres-ro:5432/myappApplication Connection
Services created automatically:
| Service | Purpose |
|---|---|
my-postgres-rw | Always points to primary (read-write) |
my-postgres-ro | Round-robin across replicas (read-only) |
my-postgres-r | Round-robin across all instances |
# Application deployment
env:
- name: DATABASE_URL
value: postgresql://myapp:$(DB_PASSWORD)@my-postgres-rw:5432/myapp
- name: DATABASE_URL_READONLY
value: postgresql://myapp:$(DB_PASSWORD)@my-postgres-ro:5432/myappMonitoring
CNPG exports 200+ metrics to Prometheus:
spec:
monitoring:
enablePodMonitor: true
customQueriesConfigMap:
- name: custom-queries
key: queriesKey metrics to alert on:
groups:
- name: cnpg-alerts
rules:
- alert: PostgreSQLReplicationLag
expr: cnpg_pg_replication_lag > 30
for: 5m
labels:
severity: warning
- alert: PostgreSQLClusterNotHealthy
expr: cnpg_collector_up == 0
for: 1m
labels:
severity: critical
- alert: PostgreSQLHighConnections
expr: cnpg_backends_total / cnpg_pg_settings_setting{name="max_connections"} > 0.8
for: 5m
labels:
severity: warningMaintenance Operations
# Switchover (planned failover)
kubectl cnpg promote my-postgres my-postgres-2
# Restart a single instance
kubectl cnpg restart my-postgres my-postgres-3
# Fence an instance (for maintenance)
kubectl cnpg fencing on my-postgres my-postgres-2
# Online resize storage
kubectl patch cluster my-postgres \
--type merge \
-p '{"spec":{"storage":{"size":"100Gi"}}}'
# Rolling update (change image)
kubectl patch cluster my-postgres \
--type merge \
-p '{"spec":{"imageName":"ghcr.io/cloudnative-pg/postgresql:16.5"}}'Production Checklist
- 3+ instances across availability zones
- Anti-affinity rules (pods on different nodes)
- Continuous WAL archiving to object storage
- Scheduled base backups (daily)
- Test PITR recovery quarterly
- PodMonitor for Prometheus metrics
- Connection pooler for high-connection apps
- Resource requests and limits set
- PDB (PodDisruptionBudget) configured
- Network policies restricting database access
# Anti-affinity and topology spread
spec:
affinity:
topologyKey: topology.kubernetes.io/zone
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/database
operator: ExistsDeepen Your Kubernetes Skills
If you found this article useful, check out my books for hands-on Kubernetes mastery:
- Kubernetes Recipes β A practical guide for container orchestration and deployment with real-world patterns
- Ansible for Kubernetes by Example β Automate Kubernetes cluster operations with Ansible playbooks
Both books follow the same practical, example-driven approach you see in my articles.