This lab designs a backup strategy for a sample workload and runs a test restore. By the end you will have a documented strategy and a verified restore procedure.
Tasks
Task 1: Define the workload
Sample workload:
- Application: PostgreSQL database with 50 GB of data.
- Criticality: business-critical. RPO 1 hour, RTO 4 hours.
- Compliance: 30 days retention, encrypted at rest.
- Operations: 1 application, 1 host, 1 region.
Task 2: Choose backup method
For PostgreSQL, take an application-consistent base backup. Two supported methods exist, and only one of them is a single command:
# Option 1 (recommended): pg_basebackup.
# It streams the WAL generated during the copy and writes
# backup_label for you, so the result is self-consistent.
pg_basebackup -D /backup/base -Ft -z -Xs -P -c fast
# Option 2: low-level file copy.
# A bare rsync of a running PGDATA is NOT a backup. It is only
# valid when bracketed in a SINGLE, continuously open session
# and paired with archived WAL:
#
# psql -c "SELECT pg_backup_start('base', fast => true);" <- session stays open
# ...copy PGDATA now...
# psql -c "SELECT * FROM pg_backup_stop();" <- returns backup_label
#
# and it requires archive_mode = on with every WAL segment
# between the start and stop LSNs retained. Write the returned
# backup_label into the copy before you call it a backup.
Choose pg_basebackup. It is not merely simpler - it removes
the two ways Option 2 is usually got wrong.
Task 3: Design 3-2-1-1-0
- 3 copies: production database, local backup, offsite backup.
- 2 media: local disk, cloud (S3).
- 1 offsite: S3 in a different region.
- 1 immutable: S3 Object Lock COMPLIANCE.
- 0 errors: quarterly test restore.
Task 4: Configure encryption
Use BorgBackup or pg_basebackup with encryption:
# BorgBackup with encryption
borg init --encryption repokey-blake2 /backup/repo
# Archive the pg_basebackup OUTPUT, not the live PGDATA
borg create /backup/repo::base-{now} /backup/base
Choose BorgBackup for the offsite copy; pg_basebackup for the local copy.
Escrow the key immediately after borg init, before the first
archive exists:
# Machine-readable export - store offline, off-site
borg key export /backup/repo /root/borg-key-backup.txt
# Printable form for a safe or paper escrow
borg key export --paper /backup/repo /root/borg-key-paper.txt
# Prove the export works before relying on it
borg key import /backup/repo /root/borg-key-backup.txt
Then move both files off the host.
Task 5: Run a test restore
Restore the most recent backup to a test host:
# On the test host. borg extract writes into the CURRENT directory, and any
# trailing argument is an in-archive path FILTER, not a destination - so cd
# there first rather than passing the target as an argument.
sudo mkdir -p /var/lib/postgresql/restore
cd /var/lib/postgresql/restore
# Borg has no '::latest' selector (that is restic's). Resolve the newest
# archive by name.
LATEST=$(borg list --last 1 --format '{archive}' /backup/repo)
echo "restoring: $LATEST"
borg extract --list /backup/repo::"$LATEST"
# PostgreSQL refuses to start unless PGDATA is owned by postgres and mode 0700
sudo chown -R postgres:postgres /var/lib/postgresql/restore
sudo chmod 0700 /var/lib/postgresql/restore
sudo -u postgres pg_ctl -D /var/lib/postgresql/restore start
sudo -u postgres psql -c 'SELECT count(*) FROM users;'
The test restore verifies that the backup is restorable, the restore procedure is correct, and the application can start with the restored data.
Task 6: Document the strategy
BACKUP STRATEGY
===============
Workload: PostgreSQL database
Application: <name>
Criticality: business-critical
RPO: 1 hour
RTO: 4 hours
Compliance: 30 days retention, encrypted
Method: pg_basebackup (consistent base) + WAL archiving,
then BorgBackup of the pg_basebackup output
NOT a bare rsync/borg of the live PGDATA
Prerequisite settings: wal_level = replica, archive_mode = on,
archive_command retaining every segment for 30 days
3-2-1-1-0:
- 3 copies: production, local backup, offsite
- 2 media: local disk, S3
- 1 offsite: S3 in different region
- 1 immutable: S3 Object Lock COMPLIANCE
- 0 errors: quarterly test restore
Schedule:
- Hourly: WAL archive
- Daily: pg_basebackup to local disk
- Daily: BorgBackup to S3 with Object Lock
Encryption:
- Local: pg_basebackup native
- Offsite: BorgBackup repokey (AES-OCB)
- Key: stored in vault, 2 copies
- Key escrow: borg key export (+ --paper) held OUTSIDE the
vault and OUTSIDE the primary site; opened once a
quarter to prove it works
- Passphrase rotation: quarterly, for hygiene only.
borg key change-passphrase re-wraps the same key; it
does NOT re-encrypt data, so an old exported key still
reads every old archive. True rotation = new repository.
Test restore:
- Quarterly: full restore to test host
- Procedure: extract from BorgBackup, start PostgreSQL, verify
- Last tested: <date>