Skip to content

Backup Bucket

The GCS bucket and IAM glue that lets CloudNativePG (CNPG) write Postgres backups out of the cluster. Provisioned by infrastructure/gcp/backup.tf.

For day-2 Postgres ops (taking a manual backup, restoring, PITR), see the Postgres operations runbook. This page covers only the GCP-side resources.

Bucket

Field Value
Name tomoda-db-backups-${project_id} (currently tomoda-db-backups-development-485000)
Location asia-east1
Uniform bucket-level access On
force_destroy False — Terraform won't delete the bucket while it has objects

Lifecycle

Age Action
30 days Delete

That's the only lifecycle rule. Anything older than 30 days disappears.

30 days is the entire backup retention

There is no transition to Nearline or Archive, no manual snapshots kept indefinitely, no off-region copy. If you need to restore from a backup older than a month, you can't. For longer retention, either lengthen the lifecycle rule or add a separate "monthly archive" bucket that copies in objects on a schedule.

What CNPG writes here

CNPG Barman writes two kinds of objects:

  1. WAL files — continuous write-ahead-log shipping for point-in-time recovery (PITR). One small file every few minutes per cluster.
  2. Base backups — full Postgres data directory dumps, taken on a schedule defined by the CNPG Cluster resource (typically daily).

Both PITR within 30 days and "restore yesterday's base backup" rely on this bucket alone. If the bucket disappears, both Postgres clusters lose recoverability immediately.

Service account

resource "google_service_account" "cnpg_backup_sa" {
  account_id   = "cnpg-backup-sa"
  display_name = "CloudNative-PG Backup Service Account"
}
  • Bound to the backup bucket with roles/storage.objectAdmin (full read/write/delete on objects in this bucket only — no other buckets, no project-level storage roles).
  • Bound to two Kubernetes service accounts via Workload Identity:
K8s SA Workload Identity member
data/postgres-dev serviceAccount:${project_id}.svc.id.goog[data/postgres-dev]
data/postgres-prod serviceAccount:${project_id}.svc.id.goog[data/postgres-prod]

Both KSAs are in the data namespace — the dev and prod Postgres clusters share a namespace and are distinguished only by KSA name. The CNPG operator creates these KSAs automatically from the Cluster resource's name (the data/postgres-dev and data/postgres-prod patterns match CNPG's KSA naming convention).

flowchart LR
    subgraph K8s["GKE · namespace: data"]
        PGDEV[postgres-dev cluster]
        PGPROD[postgres-prod cluster]
        KSADEV[KSA: postgres-dev]
        KSAPROD[KSA: postgres-prod]
        PGDEV --> KSADEV
        PGPROD --> KSAPROD
    end

    KSADEV -. Workload Identity .-> SA[cnpg-backup-sa@…]
    KSAPROD -. Workload Identity .-> SA
    SA -->|objectAdmin| BUCKET[(tomoda-db-backups-…<br/>lifecycle: delete @ 30d)]

What's not here

  • No bucket versioning. A deleted WAL file is gone for good — CNPG manages WAL pruning, but if someone runs gsutil rm -r against the bucket, recovery is gone. If you're worried about that, put a bucket-level deny rule on storage.objects.delete for everyone except the SA, or enable versioning.
  • No KMS encryption. Objects are encrypted at rest with Google-managed keys (default). Switching to CMEK is a one-block change in backup.tf but adds operational cost (key rotation, IAM on the key, etc.).
  • No cross-region replication. Single-region (asia-east1) bucket. If asia-east1 is down, the backups are also down.
  • No access logs on the bucket. Reads and writes are not audited. Enable bucket logging if compliance ever requires it.

Output

The Terraform output db_backup_bucket exposes the bucket name for use by CNPG Cluster resources and operational scripts:

output "db_backup_bucket" {
  value = google_storage_bucket.db_backups.name
}

In practice, the CNPG Cluster YAMLs hardcode this value — terraform output -raw db_backup_bucket is the source of truth when bootstrapping a new env.

See the Postgres operations runbook for the restore procedure that consumes everything on this page.