Backing Up to Google Cloud Storage: Files, Databases and VM Disks

Backing Up to Google Cloud Storage: Files, Databases and VM Disks

Reading time1 min
#Cloud#Backup#GoogleCloud#GCS#MongoDB#Automation

Backing Up to Google Cloud Storage: Files, Databases and VM Disks

Cloud Storage is a good backup target: durable, off-site, cheap in the colder classes, and with versioning, soft delete and retention locks that protect backups from the same mistakes or attackers that hit the source. A copy in a bucket is only a backup if it is versioned or immutable, runs on a schedule, and has been restored at least once. This post covers each part.

Prepare a dedicated bucket

Keep backups in their own bucket, ideally in a separate project, so a compromised application project cannot delete them.

gcloud storage buckets create gs://my-backups \
  --location=europe-west1 \
  --default-storage-class=NEARLINE \
  --uniform-bucket-level-access \
  --public-access-prevention

Choose the class by how often you restore: Nearline (30-day minimum) or Coldline (90-day minimum) suit most backups, Archive (365-day minimum) suits long-term copies you hope never to read. Deleting earlier is billed as if the minimum had passed, which matters for daily backups kept only a week. For disaster recovery, use a dual-region or multi-region location.

Protect the contents:

  • Soft delete is on by default for new buckets with a 7-day retention. Raise it (up to 90 days) so a deletion can be undone after you notice it:
    gcloud storage buckets update gs://my-backups --soft-delete-duration=30d
    
  • Object Versioning keeps the previous version when an object is overwritten or deleted. Combine it with a lifecycle rule that deletes old noncurrent versions, otherwise they accumulate forever.
  • Retention policy makes objects undeletable for a set period. Locking the policy makes it permanent, which is what you want against ransomware but cannot be undone.
  • Least privilege. A backup job that only uploads new, uniquely named files needs roles/storage.objectCreator: it can write but cannot read, overwrite or delete.

On Compute Engine, give the VM a dedicated service account with that role. Outside Google Cloud, use Workload Identity Federation where possible, or a service account key stored with tight file permissions if not.

Incremental file backups with restic

gcloud storage rsync copies only changed files, but it produces a mirror, not a history: a file deleted or encrypted on the server is deleted or replaced in the bucket on the next run. For real incremental backups with history, use a backup tool. restic supports Cloud Storage natively, deduplicates, encrypts on the client and keeps snapshots.

export GOOGLE_PROJECT_ID=my-project
export GOOGLE_APPLICATION_CREDENTIALS=/etc/restic/gcs-key.json  # omit on Compute Engine
export RESTIC_REPOSITORY=gs:my-backups:/web-01
export RESTIC_PASSWORD_FILE=/etc/restic/password

restic init
restic backup /etc /var/www --exclude-caches
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check

The first run uploads everything, later runs upload only new and changed data. Keep the repository password somewhere other than the server: without it the backups cannot be decrypted. restic needs to delete its own lock and pruned files, so give it roles/storage.objectAdmin on this bucket and rely on soft delete for protection, or use a locked retention policy only on buckets that hold plain uploads.

Restore a snapshot:

restic snapshots
restic restore latest --target /tmp/restore --include /var/www

rclone (rclone sync with --backup-dir pointing at a dated path) is an alternative when you need plain files in the bucket instead of an encrypted repository.

Database dumps: MongoDB as an example

Databases need a consistent dump, not a copy of their data files. Stream the dump straight into Cloud Storage so no local disk space is needed:

#!/usr/bin/env bash
set -euo pipefail

STAMP=$(date -u +%Y%m%dT%H%M%SZ)
URI="mongodb://backup:${MONGO_PASSWORD}@localhost:27017/?authSource=admin"

mongodump --uri="$URI" --archive --gzip --oplog \
  | gcloud storage cp - "gs://my-backups/mongodb/db-01/${STAMP}.archive.gz"

--oplog captures writes that happen during the dump, so the result is a point-in-time snapshot. It works for full dumps of a replica set member, not with --db or --collection. set -o pipefail makes the script fail if mongodump fails, instead of uploading a truncated archive. For sharded clusters mongodump is not a consistent backup method; use filesystem snapshots or your platform's backup feature. On MongoDB Atlas, use Atlas backups.

Restore:

gcloud storage cat gs://my-backups/mongodb/db-01/20261005T020000Z.archive.gz \
  | mongorestore --uri="$URI" --archive --gzip --oplogReplay --drop

The same pattern works for other engines: pg_dump -Fc, mysqldump --single-transaction or mariadb-dump piped to gcloud storage cp -. Cloud SQL has its own automated backups and point-in-time recovery; exports to Cloud Storage (gcloud sql export sql) are an extra copy, not a replacement.

Schedule it

A cron entry is enough for a single server:

# /etc/cron.d/backup
0 2 * * * root /usr/local/bin/backup-mongodb.sh >> /var/log/backup-mongodb.log 2>&1
30 2 * * * root /usr/local/bin/restic-backup.sh >> /var/log/restic.log 2>&1

A systemd timer gives you journalctl logs and Persistent=true, which runs a missed job after a reboot. Either way, alert when a backup does not happen: push a heartbeat to your monitoring at the end of the script, or alert when the newest object in the backup prefix is older than a day.

Compute Engine disks

For VMs on Google Cloud, persistent disk snapshots are incremental by design: the first is full, the following ones store only changed blocks. Attach a snapshot schedule instead of scripting it:

gcloud compute resource-policies create snapshot-schedule daily-7d \
  --region=europe-west1 --daily-schedule --start-time=02:00 \
  --max-retention-days=7

gcloud compute disks add-resource-policies web-01 \
  --zone=europe-west1-b --resource-policies=daily-7d

Snapshots are crash-consistent. For databases, still take application-level dumps. For centrally managed, policy-based backups of VMs, Cloud SQL and other services, look at Google Cloud Backup and DR.

Test restores

A backup that has never been restored is a guess. At least monthly, restore to a scratch VM or a temporary database, check that the application starts and that row counts or checksums match expectations, and write down how long it took. That number is your real recovery time.

Quick reference

# Bucket protection
gcloud storage buckets update gs://my-backups --soft-delete-duration=30d
gcloud storage buckets update gs://my-backups --versioning

# Files with history
restic -r gs:my-backups:/web-01 backup /etc /var/www
restic -r gs:my-backups:/web-01 forget --keep-daily 7 --keep-weekly 4 --prune

# Database dump streamed to the bucket and back
mongodump --uri="$URI" --archive --gzip --oplog | gcloud storage cp - gs://my-backups/mongodb/STAMP.archive.gz
gcloud storage cat gs://my-backups/mongodb/STAMP.archive.gz | mongorestore --uri="$URI" --archive --gzip --oplogReplay

# VM disks
gcloud compute disks add-resource-policies DISK --zone=ZONE --resource-policies=daily-7d