ADR-061: Databases are backed up by the CloudNativePG Barman Cloud plugin to a bucket resource; a point-in-time restore is a new generation next to the running one
Status: Accepted (2026-10-27)
Builds on: ADR-057: Resources are named and typed; each type has a fixed contract; any resource instance may be external (named, typed resources; the Secret <system>-<name>-app), ADR-058: Images and operators are pinned exactly per release; the platform installs the operators a cluster needs as Argo CD applications (pinned versions, operators under operators/), ADR-059: An in-cluster bucket is its own SeaweedFS, run by the platform-installed SeaweedFS operator; its keys are generated in the cluster and Argo CD never deletes it (the bucket resource), spec 117; used by spec 121 (a fresh backup before a major upgrade)
Context
Section titled “Context”Spec 117 adds continuous, point-in-time backups for in-cluster databases (CloudNativePG clusters <system>-<name>) to a bucket resource, an on-demand backup, and a restore that runs next to the live database and is switched to in a second step. The components must keep using the Secret <system>-<name>-app without a change to their configuration.
Facts that shape the decision:
- CloudNativePG deprecated its built-in Barman Cloud support; the Barman Cloud plugin (
plugin-barman-cloudchart,ObjectStoreresource) is the supported path from 1.26 on, and it needs cert-manager for the TLS between plugin and operator. ObjectStoreand Velero locations take the bucket, the endpoint and the region as literal values, not from a Secret. An external bucket’s coordinates live only in the secret store today.- CloudNativePG cannot rename a cluster, and it writes and owns
<cluster>-app(host<cluster>-rw). A restored cluster has another name, so its own Secret and host differ. - CloudNativePG refuses to archive into a server path that already holds WAL of another cluster, so every restored cluster needs a new server name.
- Argo CD prunes nothing in the addon applications (
prune: false) but heals every object that git describes. Anything that must survive a re-sync has to be in git.
Decision
Section titled “Decision”- The Barman Cloud plugin archives WAL and takes base backups. A database instance with
backupgets anObjectStore<system>-<name>(destinations3://<bucket>/databases, the bucket’s Secret for the keys,retentionPolicy=retention), the plugin as WAL archiver on its active cluster, and aScheduledBackup<system>-<name>(immediate: true, methodplugin). The plugin chart and cert-manager are platform operators underoperators/(ADR-058), rendered only when a database instance in an environment withoutoperators: ownersetsbackup./resource:backupcreates oneBackupobject and waits for it. - An external bucket used as a backup target declares its non-secret coordinates in git (
remote.s3: {endpoint, bucket, region, forcePathStyle}). The keys still come only from the secret store. - A restore creates a new generation. The original cluster is generation 0 (
<system>-<name>); restore n is the cluster<system>-<name>-g<n>, bootstrapped byrecoveryfrom the bucket (targetTime, or no target forlatest), with its owner password taken from<system>-<name>-app. Generation numbers are never reused, so each cluster archives under its own server name. The restored generation archives nothing until it is switched to. - The generations live in git, in
applications/<system>/addons/<env>/<name>/restore-state.yaml, which onlycplat resource restorewrites andrender.pyreads, like the image pins in the overlays. Each restore action is one commit: in a cluster that Argo CD manages, a pull request that the command merges after CI and then waits for; in a cluster without Argo CD (the end-to-end test), a local commit andkubectl apply -kof the addon directory. - A switch detaches the connection Secret.
--switchmakes the restored generation active in git (WAL archiving and the scheduled backup move to it), removes the owner reference of<system>-<name>-appand rewrites its host keys to the new-rwservice, then restarts the using components’ pods. From then on no operator manages that Secret: every generation is created withrecovery.secret: <system>-<name>-app, so CloudNativePG never writes it again. The old generation stays aspreviousuntil--discard, which removes it from git and deletes the cluster and its volumes; the backups in the bucket are never deleted by a restore command.
Consequences
Section titled “Consequences”- A cluster that needs backups runs two more platform operators (the plugin in
cnpg-system, cert-manager). A cluster whose owner already runs cert-manager setsoperators: ownerand installs the plugin itself. - Restore points before a switch are read from the old generation’s server name; once a
previousgeneration is discarded, nothing prunes its old backups any more (they stay in the bucket, by design never deleted by the platform). - After the first switch, the connection Secret of that database is outside every operator. Rebuilding the environment from git alone needs that Secret back (a later spec may own it).
- A database without
backupand without restores renders byte-identically to spec 115.
Not included
Section titled “Not included”- Logical dumps, single-table restore, restore into another environment or system (spec 117 non-goals).
- Backups of external databases: their provider does it.
- Encryption beyond what the bucket provides, replication of backups.