CloudNativePG 1.30.1, released on 23 September 2026, is the current release of the PostgreSQL operator that the CNCF moved to Incubating status on 2 November 2026. Production teams tend to hit the same problems: two instances on one node because anti-affinity was left at its soft default, a primary whose pg_wal fills because WAL archiving stopped, and backups still configured through .spec.backup.barmanObjectStore, the in-tree Barman Cloud integration that has been deprecated since 1.26 and is removed starting from 1.32.0. This guide covers the production Cluster manifest, replication, failover, backups, PITR, pooling, upgrades, and monitoring, with the kubectl cnpg command for each.
- Run CloudNativePG 1.30.x (1.30.1 as of November 2026). 1.29 reached end of life on 29 September 2026, and 1.31 is planned for around December 2026.
- Back up new clusters with the Barman Cloud Plugin (CNPG-I) and a
minimalorstandardimage. NativebarmanObjectStoresupport is removed starting from 1.32.0, and thesystemimages go with it. - On three instances, set
synchronoustomethod: anyandnumber: 1, then choosedataDurability: required(writes pause without a synchronous standby) orpreferred(writes continue). - Major upgrades are declarative since 1.26: change the image's major version and the operator runs
pg_upgrade --linkoffline. The whole cluster is down, replicas are re-cloned, and older backups cannot be used for PITR past the upgrade.
Scope and review date: November 2026. This post covers CloudNativePG 1.30.1 with the Barman Cloud Plugin 0.15.1, on Kubernetes 1.34 to 1.36 and PostgreSQL 14 to 18. Manifests, commands, and defaults were checked against the CloudNativePG 1.30 documentation, the plugin documentation, and the release notes on 9 November 2026. They were not run in a lab, so rehearse them on a staging cluster first.
Which CloudNativePG version should you run in November 2026?
Run 1.30.x. The supported releases page lists 1.30 (released 29 June 2026) as supported until about March 2027, and 1.29 as ending on 29 September 2026. A minor release stays supported until three months after the next minor ships, and 1.31.0 is planned for around December 2026. CloudNativePG 1.30 supports Kubernetes 1.34, 1.35, and 1.36 and PostgreSQL 14 to 18, and its support for PostgreSQL 14 ends on 12 November 2026.
The project was accepted into the CNCF on 21 January 2025 and, according to the CNCF project page, moved to the Incubating maturity level on 2 November 2026.
# Install the operator (the documented command uses server-side apply)
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.1.yaml
kubectl rollout status deployment -n cnpg-system cnpg-controller-manager
# Install the kubectl plugin with Krew
kubectl krew install cnpgInterpretation: the rollout command returns once the controller is running in cnpg-system. We keep the kubectl cnpg plugin on the same minor version as the operator, so its commands match the API the operator serves.
The default image in 1.30.1 is 18.6-system-trixie. The system flavor only adds the in-tree Barman Cloud binaries and is deprecated, so pin a standard or minimal tag instead.
Should you run CloudNativePG or Patroni on VMs?
Both run PostgreSQL streaming replication. The difference is who owns leader election, routing, and backups. Patroni keeps cluster state in a distributed configuration store (etcd, Consul, ZooKeeper, or the Kubernetes API) and leaves routing and backups to tools you choose. CloudNativePG uses the Kubernetes API directly, manages PVCs itself instead of StatefulSets, and declares backups, poolers, and upgrades as resources.
CloudNativePG and Patroni on VMs side by side
Switch tabs to compare who handles each operational job
CloudNativePG 1.30
| Job | CloudNativePG 1.30 |
|---|---|
| Leader election | Kubernetes API; a per-cluster Lease gates promotion |
| Client routing | -rw, -ro, and -r Services moved by the operator |
| Synchronous replication | .spec.postgresql.synchronous with dataDurability |
| Backups and PITR | Backup and ScheduledBackup resources through a CNPG-I plugin or volume snapshots |
| Planned switchover | kubectl cnpg promote |
| Major version upgrade | Declarative offline pg_upgrade by changing the image |
Patroni on VMs
| Job | Patroni on VMs |
|---|---|
| Leader election | A DCS you run: usually a three-node etcd or Consul cluster |
| Client routing | HAProxy health checks against the Patroni REST API, a VIP, or DNS |
| Synchronous replication | synchronous_mode, synchronous_mode_strict, synchronous_node_count |
| Backups and PITR | A separate tool you install and schedule, such as pgBackRest or WAL-G |
| Planned switchover | patronictl switchover |
| Major version upgrade | A pg_upgrade runbook you script around Patroni |
Rule of thumb: if your team already runs stateful workloads on Kubernetes with tested StorageClasses, CloudNativePG removes the DCS, the proxy, and the backup scheduler. If not, Patroni on VMs keeps fewer layers between PostgreSQL and the disk.
Patroni's settings are in its replication modes documentation. For the VM side, see our posts on a production Patroni cluster and Patroni with Consul as the DCS.
What does a production Cluster manifest need?
The Cluster resource defines one primary and any number of replicas. The operator creates a -rw Service that always points at the primary, a -ro Service for replicas only, and an -r Service for any instance. Applications use the generated pg-prod-app Secret for credentials.
What one CloudNativePG Cluster creates
Applications reach PostgreSQL only through Services; the primary ships WAL to object storage
After a failover: the operator repoints pg-prod-rw at the promoted replica, so applications that connect through the Service, not a pod IP, follow the new primary without a config change.
A starting manifest for three instances spread across zones, with WAL on its own volume and backups through the plugin:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-prod
namespace: prod
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:18.6-standard-trixie
primaryUpdateStrategy: unsupervised
primaryUpdateMethod: switchover
postgresql:
parameters:
max_connections: "200"
shared_buffers: "2GB"
synchronous:
method: any
number: 1
dataDurability: required
failoverQuorum: true
resources:
requests:
cpu: "2"
memory: 8Gi
limits:
cpu: "2"
memory: 8Gi
affinity:
enablePodAntiAffinity: true
topologyKey: topology.kubernetes.io/zone
podAntiAffinityType: required
storage:
storageClass: fast-ssd
size: 200Gi
walStorage:
storageClass: fast-ssd
size: 50Gi
plugins:
- name: barman-cloud.cloudnative-pg.io
isWALArchiver: true
parameters:
barmanObjectName: pg-prod-storeInterpretation: parameter values are strings. The anti-affinity default is preferred on kubernetes.io/hostname, which lets the scheduler co-locate instances when short of capacity. required on a zone key needs three schedulable zones, or a pod stays Pending. Equal requests and limits put the pods in the QoS class that the kubelet evicts last.
Storage and WAL volumes
The storage documentation gives the reasons for a separate WAL volume: WAL and data files stop competing for I/O, and a full PGDATA volume cannot block WAL writes. walStorage cannot be removed once added. Raising size resizes every PVC only if the StorageClass has allowVolumeExpansion: true; otherwise you re-create PVCs one instance at a time.
How do you configure synchronous replication?
Synchronous replication is off until you define .spec.postgresql.synchronous, and CloudNativePG does not let you set synchronous_standby_names directly. Two fields are mandatory: method (any for quorum, first for priority) and number. The replication documentation calls any the most common setup.
dataDurability decides what happens when standbys disappear. With required, the default, writes pause until enough synchronous standbys return. With preferred, the operator lowers the requested number to the healthy standbys it has, down to none, and writes continue with a risk of losing recent commits on a primary failure. preferred cannot be combined with standbyNamesPre or standbyNamesPost.
kubectl cnpg psql pg-prod -n prod -- -c "SHOW synchronous_standby_names"Interpretation: with the manifest above, expect ANY 1 ("pg-prod-2","pg-prod-3","pg-prod-1"). The primary's own name is listed last. An empty value means synchronous replication is not configured, or preferred mode has dropped every standby.
failoverQuorum: true adds a check. Under ANY 1, a commit is on the primary and one standby; if both fail together, the remaining replica may lack acknowledged transactions. With failover quorum, the operator promotes only when it can confirm the candidate holds all synchronously committed data. It replaces the deprecated alpha.cnpg.io/failoverQuorum annotation.
How do failover and switchover work?
When the primary stays unhealthy longer than .spec.failoverDelay (default 0 seconds), the operator shuts the old primary down so the WAL receivers stop, then promotes the most up-to-date replica. The old primary tries a fast shutdown first, bounded by .spec.switchoverDelay (default 3600 seconds), so pending WAL can be archived.
Since 1.30, a Kubernetes Lease named after the cluster serializes promotion. An instance must hold it to act as primary. After a crash, a replica must wait out leaseDurationSeconds (default 15) before it can promote. The failover documentation also puts node-level detection at about 40 to 55 seconds with stock kube-controller-manager settings, because the operator waits for the node lifecycle controller to mark the pod not ready.
# Who holds the primary lease right now?
kubectl get lease pg-prod -n prod
# Planned switchover to instance 2 (for node maintenance or testing)
kubectl cnpg promote pg-prod pg-prod-2 -n prodInterpretation: the HOLDER column names the current primary pod. promote performs a switchover: the former primary shuts down cleanly, releases the lease, and rejoins as a replica, while pg-prod-rw moves to the new primary. Expect clients to reconnect.
kubectl cnpg status on a healthy three-instance cluster
Illustrative output, shaped like the CloudNativePG 1.30 plugin; check archiving and roles first
What to read first: a rising "WALs waiting to be archived" count means archiving is failing and pg_wal will fill. Quorum standbys also show as "Standby (sync)". See the kubectl plugin documentation.
How do you set up backups with the Barman Cloud Plugin?
Since 1.26, backup and recovery have been moving out of the operator into CNPG-I plugins. Removal of native Barman Cloud support has been postponed several times; the 1.30.0 release notes said 1.31.0, and the current backup documentation now says 1.31 is the last minor with native support, removed starting from 1.32.0. The default method on Backup resources is still barmanObjectStore, so set method: plugin explicitly.
The plugin needs cert-manager (or certificates you supply), must be installed in the operator's namespace, and version 0.15.1 requires CloudNativePG 1.26 or later. It runs as a sidecar in each instance pod. Its ObjectStore resource takes the same configuration as the old in-tree stanza, and retention moves there too:
apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: pg-prod-store
namespace: prod
spec:
retentionPolicy: "30d"
configuration:
destinationPath: s3://example-pg-backups/
s3Credentials:
accessKeyId:
name: pg-backup-s3
key: ACCESS_KEY_ID
secretAccessKey:
name: pg-backup-s3
key: ACCESS_SECRET_KEY
wal:
compression: gzipInterpretation: 30d is a recovery window. The plugin keeps the newest backup older than 30 days plus everything after it, so you can restore to any point in that window. Leave serverName empty here; set it as a plugin parameter in the Cluster when you need it.
Schedule base backups with a ScheduledBackup. Its cron expression has six fields, starting with seconds:
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: pg-prod-daily
namespace: prod
spec:
schedule: "0 0 2 * * *" # 02:00:00 every day
immediate: true
backupOwnerReference: self
cluster:
name: pg-prod
method: plugin
pluginConfiguration:
name: barman-cloud.cloudnative-pg.ioInterpretation: by default the backup runs on the most up-to-date standby (prefer-standby), away from the primary's I/O. immediate: true takes the first backup on creation. The cluster reference is immutable from 1.30.
Backups do not include the superuser or application user Secrets. Cover them in your Kubernetes-level backups.
To migrate, follow the plugin's migration guide: build the ObjectStore from your old stanza, swap the stanza for the plugin in one change (a rolling update), then update every ScheduledBackup and externalClusters entry.
How do you restore to a point in time?
CloudNativePG never restores in place. Recovery bootstraps a new Cluster from a base backup and replays archived WAL up to a recoveryTarget, while the original cluster keeps running. If you give only targetTime, the operator picks the closest backup completed before that time. Timestamps without a time zone are read as UTC, so always write one.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-restore
namespace: prod
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:18.6-standard-trixie
storage:
storageClass: fast-ssd
size: 200Gi
walStorage:
storageClass: fast-ssd
size: 50Gi
bootstrap:
recovery:
source: origin
recoveryTarget:
targetTime: "2026-11-02T09:58:00Z"
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: pg-prod-store
serverName: pg-prodInterpretation: serverName points at the source cluster's folder in the bucket. Without a plugins section, the restored cluster does not archive WAL. If no transaction committed after the target time, recovery fails, as the recovery documentation warns.
PITR runbook: restore pg-prod to a new cluster
Seven steps; steps 5 and 6 are the write outage, only if you cut over
-
Fix the target time
Agree the last good moment with the application owner; write it in RFC 3339 with a zone.
-
Confirm a backup and WAL cover it
A completed backup must predate the target, and archiving must have been working since.
kubectl get backups.postgresql.cnpg.io -n prod kubectl cnpg status pg-prod -n prod -
Apply the recovery Cluster
Use a new name. The source cluster keeps serving traffic while the restore runs.
kubectl apply -f pg-restore.yaml -
Validate the restored data
Check the rows the incident affected before anyone decides to cut over.
kubectl cnpg psql pg-restore -n prod -- app \ -c "SELECT max(updated_at) FROM orders" -
Stop writes to pg-prod
Outage startsPause the Pooler so PgBouncer finishes running queries and holds new ones.
kubectl patch pooler pooler-pg-prod-rw -n prod \ --type merge -p '{"spec":{"paused":true}}' -
Point applications at pg-restore
Outage endsA Pooler's cluster reference is immutable, so create a new Pooler for pg-restore or switch the connection string to pg-restore-rw.
-
Turn on archiving and back up
Add the plugins section to pg-restore and take a base backup, so it has its own recovery point.
kubectl cnpg backup pg-restore -n prod \ --method=plugin \ --plugin-name=barman-cloud.cloudnative-pg.io
Rollback point: until step 6, pg-prod is untouched, so resuming the Pooler undoes the cutover. Keep pg-prod until the restored cluster has a completed backup of its own.
How do you add PgBouncer with the Pooler resource?
A Pooler is a Deployment of PgBouncer pods in front of one of the cluster's Services. It needs PgBouncer 1.19 or later, must live in the same namespace as the cluster, and cannot share a name with a cluster there. Deleting the Cluster does not delete its poolers. Authentication details are in the connection pooling documentation.
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pooler-pg-prod-rw
namespace: prod
spec:
cluster:
name: pg-prod
instances: 3
type: rw
pgbouncer:
poolMode: transaction
parameters:
max_client_conn: "2000"
default_pool_size: "40"Interpretation: applications connect to the pooler-pg-prod-rw Service instead of pg-prod-rw. Each PgBouncer pod accepts up to 2,000 client connections and opens up to 40 server connections per user and database pair, so three pods can open 120 per pair. Size that against max_connections. Transaction mode breaks session state such as SET and advisory locks, so check the application first.
Setting paused: true runs PgBouncer's PAUSE, as in step 5 of the runbook above. Metrics are on port 9127; alert first on cnpg_pgbouncer_pools_cl_waiting.
How do minor and major PostgreSQL upgrades work?
For a minor upgrade, change imageName or the image catalog. The operator replaces replicas one at a time and the primary last; with primaryUpdateMethod: switchover, that last step is a switchover to an upgraded replica, so nothing waits on an image pull. With switchover, you cannot change the image and PostgreSQL parameters in the same edit: the operator rejects it with a validation error, so make them two changes.
For a major upgrade, CloudNativePG has supported a declarative offline in-place upgrade since 1.26. Change the major version in the image tag, keeping the same Debian release (a bookworm image cannot upgrade to trixie). The upgrade documentation describes the sequence: every pod shuts down, a pg-prod-1-major-upgrade Job runs pg_upgrade --link on the primary's volumes, then the replica PVCs are deleted and the replicas are re-cloned.
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:18.6-standard-trixie # was 16.x-standard-trixie
plugins:
- name: barman-cloud.cloudnative-pg.io
isWALArchiver: true
parameters:
barmanObjectName: pg-prod-store
serverName: pg-prod-pg18 # new archive folder for the new majorInterpretation: pg_upgrade creates a new system identifier and restarts at timeline 1. The plugin does not separate archives on its own, so change serverName in the same edit to keep the old archive intact. If the Job fails, set the image back to the old major and the operator deletes the Job and restarts on the original data.
The whole cluster is unavailable during the upgrade, and re-cloning replicas takes time on large databases. Pre-upgrade backups cannot be used for PITR after it, so take a base backup straight away, and run ANALYZE because statistics are not carried over. On PostgreSQL 17.0 to 17.5, upgrades fail when max_slot_wal_keep_size is not -1; move to 17.6 first.
What should you monitor, and which kubectl cnpg commands matter?
Each instance exposes Prometheus metrics on port 9187, named metrics. The monitoring documentation recommends creating your own PodMonitor. The .spec.monitoring.enablePodMonitor field on Cluster and Pooler is deprecated. Query results are cached for 30 seconds by default.
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: pg-prod
namespace: prod
spec:
selector:
matchLabels:
cnpg.io/cluster: pg-prod
podMetricsEndpoints:
- port: metricsInterpretation: the cnpg.io/cluster label selects every instance pod of pg-prod, whichever is primary at the time.
Start with three alerts. The metric names come from the operator's default monitoring queries; the thresholds are starting points to tune:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: pg-prod-alerts
namespace: prod
spec:
groups:
- name: cloudnativepg
rules:
- alert: PostgresDown
expr: cnpg_collector_up == 0
for: 1m
- alert: WalArchivingFailing
expr: increase(cnpg_pg_stat_archiver_failed_count[10m]) > 0
for: 10m
- alert: ReplicaLagging
expr: cnpg_pg_replication_lag > 60
for: 5mInterpretation: archiving failures matter most, because unarchived WAL stays in pg_wal until the volume fills. CloudNativePG sets archive_timeout to 5 minutes, which is where its documented recovery point objective of 5 minutes or less comes from. Also graph cnpg_collector_nodes_used: below the instance count, instances share a node.
Beyond status and promote, use kubectl cnpg report cluster pg-prod -n prod -f report.zip --logs to bundle manifests, events, and logs for a support ticket before you delete anything.
Frequently asked questions
Is the CloudNativePG barmanObjectStore backup method deprecated?
Yes. Native Barman Cloud support has been deprecated since CloudNativePG 1.26, and the current documentation says 1.31 is the last minor release that includes it, with removal starting from 1.32.0. Migrate to the Barman Cloud Plugin, which uses an ObjectStore resource and method: plugin on Backup and ScheduledBackup resources, before upgrading to 1.32.
Can CloudNativePG do a PostgreSQL major version upgrade in place?
Yes, since version 1.26. Change the major version in the cluster's image and the operator shuts down every pod, runs pg_upgrade with --link in a Job, and re-clones the replicas. It is an offline upgrade, so the whole cluster is unavailable during it. Take a new base backup afterwards, because older backups cannot be used for PITR across the upgrade.
Does CloudNativePG need etcd or Consul like Patroni?
No. CloudNativePG uses the Kubernetes API server to store cluster state and coordinate failover, and since 1.30 it uses a Kubernetes Lease per cluster to gate promotion. Patroni needs a distributed configuration store such as etcd, Consul, ZooKeeper, or the Kubernetes API, which you run and monitor yourself on VMs.
How do I trigger a switchover in CloudNativePG?
Run kubectl cnpg promote with the cluster name and the target instance, for example kubectl cnpg promote pg-prod pg-prod-2. The current primary shuts down cleanly, releases its lease, and rejoins as a replica, and the operator moves the rw Service to the new primary. Clients must reconnect.
Key takeaways
- Run CloudNativePG 1.30.x with a pinned
standardorminimalimage, and keep thekubectl cnpgplugin on the same minor. - Set required anti-affinity on a zone or host key, a separate
walStoragevolume, and equal CPU and memory requests and limits before go-live. - Choose
dataDurabilitydeliberately:requiredpauses writes when standbys are missing;preferredkeeps writing and can lose recent commits. - Move backups to the Barman Cloud Plugin now, schedule them with six-field cron, and rehearse PITR into a new cluster.
- Treat major upgrades as a full-cluster outage: change
serverNamewith the image, then take a new backup and runANALYZE.
Run PostgreSQL on Kubernetes with JusDB
JusDB's PostgreSQL on Kubernetes team can review your CloudNativePG Cluster manifests, storage classes, and anti-affinity rules, plan a move from in-tree Barman Cloud to the plugin, and write and rehearse PITR and major upgrade runbooks with you.
Bring your manifests, operator and PostgreSQL versions, backup configuration, and agreed recovery targets. Your team approves every production change.