PostgreSQL Backup and Disaster Recovery
How the platform's shared PostgreSQL cluster is backed up, rotated, and restored.
This playbook explains how the platform's shared CNPG PostgreSQL cluster is backed up, how backup credentials are rotated safely, and what to check when planning a restore.
Backup Model
The CNPG cluster backs up to an S3-compatible bucket on Rook-Ceph RGW using CNPG's barmanObjectStore method. Backups are controlled through two pieces of configuration in the cnpg component's [backups] table:
[backups]
enabled = true
schedule = "0 0 2 * * *" # daily at 02:00
name = "daily-backup"
retention = "30d"
access_key_id = {value = "", secret = true, generate = "s3_access_key_id"}
access_secret_key = {value = "", secret = true, generate = "s3_secret_access_key"}A ScheduledBackup custom resource is rendered from this configuration and applied at ArgoCD sync-wave 2, ahead of the cluster's other post-sync hooks. It intentionally omits spec.immediate — the CNPG Helm chart hardcodes immediate: true, which would trigger an unwanted backup on every sync; managing the ScheduledBackup as a separate manifest avoids that.
Backup credentials (access_key_id / access_secret_key) are generated automatically if left empty and are propagated to Rook-Ceph as a dedicated cnpgbackups object-store user via Reflector.
Credential Rotation Safety
Because the backup S3 credentials are shared between the CNPG Cluster resource and the underlying Rook-Ceph object-store user, changing them requires the two to be updated in lockstep. A PostSync hook Job (cnpg-backup-rotation) automates this:
- On every sync, it hashes the current
ACCESS_KEY_ID/ACCESS_SECRET_KEYin thecnpg-backups-s3-credentialssecret. - On first install, it records that hash on the
Clusterresource and exits without restarting anything. - On later syncs, if the hash is unchanged, it is a no-op.
- If the hash has changed (the credentials were rotated), it waits for
Reflectorto mirror the new secret into therook-cephnamespace, waits for the correspondingCephObjectStoreUserto reachReady, and only then triggers a CNPG-managed rolling restart (via thekubectl.kubernetes.io/restartedAtannotation) so the cluster picks up the new credentials without a manual, unsynchronized restart. - It waits for the cluster to report
Cluster in healthy statewith all instances ready before recording the new hash and completing.
This ordering exists specifically to avoid a rolling restart being triggered before Rook-Ceph has actually finished propagating rotated credentials, which would otherwise leave CNPG unable to reach its own backup target mid-restart.
Retention
Backups are retained according to the retention value (30d by default), enforced by CNPG's own retention policy against the barmanObjectStore. Retention is a property of the Cluster resource, so changing it takes effect on the next reconcile without requiring a new backup to be taken immediately.
Restore Considerations
CNPG restores are performed by bootstrapping a new Cluster from an existing backup (CNPG does not support in-place restore of a running cluster). Before performing a restore:
- Confirm which components share this CNPG instance — the shared pooler is used by
Keycloak,Grafana,LiteLLM, the CoreAI LLM backend,MLflow,Airbyte,Supersetso a restore affects all of their state simultaneously; there is no per-database restore path at the CNPG level. - Confirm the target recovery point is covered by the
retentionwindow. - Confirm the
cnpg-backups-s3-credentialssecret used for the restore matches the credentials active on the Rook-Ceph object-store user at the time of the backup being restored, not necessarily the current ones if a rotation happened since.