commit 448e31c01b744fd2e891cde4aa90f11ac50656f0 Author: Per Magnus Petersen Date: Fri Mar 6 15:01:39 2026 +0100 Add PostgreSQL with automated hot backup setup Weekly full backups via pg_basebackup and continuous WAL archiving for point-in-time recovery, running as Docker Compose services. Co-Authored-By: Claude Opus 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f113c6 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# PostgreSQL with Hot Backup + +PostgreSQL 18 with automated weekly full backups and continuous WAL archiving for point-in-time recovery. + +## Services + +| Service | Description | +|---------|-------------| +| `postgres-db` | PostgreSQL 18 with WAL archiving enabled | +| `postgres-backup` | Sidecar that runs weekly full backups via `pg_basebackup` | + +## Quick Start + +```bash +cp .env.example .env # edit with your credentials +docker compose up -d +``` + +## Configuration + +Environment variables in `.env`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `POSTGRES_USER` | Database user | `postgres` | +| `POSTGRES_PASSWORD` | Database password | — | +| `POSTGRES_DB` | Default database name | `postgres` | +| `PGPORT` | Host port for PostgreSQL | `5432` | + +## Backup Strategy + +### Full Backups (Weekly) +- `pg_basebackup` runs on startup, then every 7 days +- Stored in `/data/backup/postgres/full/full_/` +- Hot backup — no downtime required + +### Incremental via WAL Archiving (Continuous) +- PostgreSQL archives Write-Ahead Log segments as they complete (~16MB each) +- Stored in `/data/backup/postgres/wal/` +- Captures every transaction between full backups + +### Backup Location + +``` +/data/backup/postgres/ +├── full/ +│ └── full_2026-03-06_12-00-00/ # weekly full snapshots +└── wal/ + ├── 000000010000000000000001 # archived WAL segments + └── ... +``` + +## Restore + +To perform point-in-time recovery: + +1. Stop PostgreSQL +2. Replace the data directory with a full backup: + ```bash + cp -r /data/backup/postgres/full/full_/ /path/to/pgdata/ + ``` +3. Create a `recovery.signal` file in the data directory +4. Configure `restore_command` in `postgresql.conf`: + ``` + restore_command = 'cp /data/backup/postgres/wal/%f %p' + ``` +5. Optionally set `recovery_target_time` for a specific point in time: + ``` + recovery_target_time = '2026-03-06 14:30:00' + ``` +6. Start PostgreSQL — it will replay WAL files and recover + +## Monitoring + +```bash +# Check backup service logs +docker compose logs postgres-backup + +# Check WAL archiving activity +docker compose logs postgres-db | grep archive + +# List full backups +ls /data/backup/postgres/full/ + +# List archived WAL files +ls /data/backup/postgres/wal/ +``` + +## Volumes + +| Volume/Mount | Purpose | +|-------------|---------| +| `postgres_data` (Docker volume) | PostgreSQL data directory | +| `/data/backup/postgres/wal` | Archived WAL segments | +| `/data/backup/postgres` | All backups (full + WAL) | diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ec65513 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ + +services: + postgres-db: + image: postgres:18 + container_name: postgres-db + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "${PGPORT}:5432" + volumes: + - postgres_data:/var/lib/postgresql + - /data/backup/postgres/wal:/backup/wal + - ./init-replication-hba.sh:/docker-entrypoint-initdb.d/init-replication-hba.sh:ro + command: + - postgres + - -c + - wal_level=replica + - -c + - archive_mode=on + - -c + - archive_command=test ! -f /backup/wal/%f && cp %p /backup/wal/%f + + postgres-backup: + image: postgres:18 + container_name: postgres-backup + restart: unless-stopped + depends_on: + - postgres-db + environment: + POSTGRES_USER: ${POSTGRES_USER} + PGPASSWORD: ${POSTGRES_PASSWORD} + volumes: + - /data/backup/postgres:/backup + entrypoint: ["/bin/bash", "-c"] + command: + - | + mkdir -p /backup/wal /backup/full + while true; do + STAMP=$$(date +%Y-%m-%d_%H-%M-%S) + echo "Starting full backup: full_$$STAMP" + pg_basebackup -h postgres-db -U "$$POSTGRES_USER" -D "/backup/full/full_$$STAMP" -Fp -Xs -P + echo "Full backup complete: full_$$STAMP" + sleep 604800 + done + +volumes: + postgres_data: diff --git a/init-replication-hba.sh b/init-replication-hba.sh new file mode 100644 index 0000000..3109e0d --- /dev/null +++ b/init-replication-hba.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Allow replication connections from the Docker network +echo "host replication all all scram-sha-256" >> "$PGDATA/pg_hba.conf"