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:
- WAL files — continuous write-ahead-log shipping for point-in-time recovery (PITR). One small file every few minutes per cluster.
- Base backups — full Postgres data directory dumps, taken on a schedule defined by the CNPG
Clusterresource (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 -ragainst the bucket, recovery is gone. If you're worried about that, put a bucket-level deny rule onstorage.objects.deletefor 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.tfbut adds operational cost (key rotation, IAM on the key, etc.). - No cross-region replication. Single-region (
asia-east1) bucket. Ifasia-east1is 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.