Skip to content

8. Scheduled backups

Goal: schedule automatic base backups and confirm that WAL archiving to R2 is actually working. Without WAL archiving you have no point-in-time recovery.

Two things must both work

flowchart LR
    cl["Cluster (plugin: barman-cloud)"] -->|continuous| wal["WAL archive → R2"]
    sb["ScheduledBackup (cron)"] -->|periodic| base["Base backup → R2"]
    wal & base --> store[("R2 bucket")]
    store --> pitr["PITR = base + replayed WAL"]
  • WAL archiving is continuous and was enabled the moment the Cluster referenced the plugin with isWALArchiver: true. It is the film.
  • Base backups are periodic full snapshots. They are the photos. You schedule them with a ScheduledBackup.

You need both.

Step 8.1 — Create a ScheduledBackup

scheduled-backup.yaml
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: pg-daily
  namespace: production
spec:
  schedule: "0 0 2 * * *"          # (1)!
  backupOwnerReference: self
  cluster:
    name: pg
  method: plugin                   # (2)!
  pluginConfiguration:
    name: barman-cloud.cloudnative-pg.io
  target: prefer-standby           # (3)!
  1. CNPG cron has 6 fields (the leading one is seconds). 0 0 2 * * * = 02:00 every day. Don't drop the seconds field.
  2. Plugin method, not the deprecated in-tree barmanObjectStore. The plugin already knows the destination from the Cluster's barmanObjectName.
  3. Back up from a standby when possible, to avoid loading the primary. (Older guides say target: primaryprefer-standby is the kinder default.)
kubectl apply -f scheduled-backup.yaml
kubectl get scheduledbackup -n production

Step 8.2 — Trigger a backup now (don't wait for 2 AM)

kubectl cnpg backup pg -n production           # on-demand backup
kubectl get backup -n production -w            # watch it reach 'completed'

Step 8.3 — Verify WAL archiving is healthy

This is the check people skip and regret.

kubectl cnpg status pg -n production | grep -A5 -i "archiv"

You want to see continuous archiving reporting OK / working and a recent "Last Archived WAL" timestamp. Also confirm objects are actually landing in R2:

aws s3 ls --endpoint-url https://<account-id>.r2.cloudflarestorage.com \
  s3://<your-bucket>/pg-cluster/ --recursive | tail

You should see base/ and wals/ prefixes growing.

If WAL archiving shows a failure

Common cause on R2: the checksum env vars are missing or the bucket/endpoint is wrong. Re-check the ObjectStore. A failing WAL archive means no PITR — fix it before putting real data in.

A note on retention

Retention (30d in our ObjectStore) is enforced by the plugin: WAL and base backups older than the window are pruned. For extra safety against accidental deletes, you can also configure an R2 lifecycle/versioning policy so deleted objects are recoverable for a few days beyond Barman's retention.

What could go wrong

  • method: barmanObjectStore in the ScheduledBackup → that's the deprecated path; use method: plugin.
  • 5-field cron → CNPG expects 6 fields (seconds first); a 5-field schedule is rejected or misread.
  • Backup completes but WAL archive fails → you have base photos but no film; PITR to arbitrary times won't work. Treat WAL archive OK as the real success signal.

Where to go deeper

Next: Security.