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 - -c - archive_timeout=600 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 chown 999:999 /backup/wal FULL_RETENTION=5 while true; do STAMP=$$(date +%Y-%m-%d_%H-%M-%S) DEST="/backup/full/full_$$STAMP" echo "Starting full backup: full_$$STAMP" if pg_basebackup -h postgres-db -U "$$POSTGRES_USER" -D "$$DEST" -Fp -Xs -P; then echo "Full backup complete: full_$$STAMP" LATEST_BACKUP_MARKER=$$(ls /backup/wal/*.backup 2>/dev/null | sort | tail -n1) if [ -n "$$LATEST_BACKUP_MARKER" ]; then echo "Cleaning WAL older than $$(basename "$$LATEST_BACKUP_MARKER")" pg_archivecleanup /backup/wal "$$(basename "$$LATEST_BACKUP_MARKER")" fi ls -1dt /backup/full/full_* | tail -n +$$((FULL_RETENTION + 1)) | while read -r old; do echo "Removing old full backup: $$old" rm -rf "$$old" done else echo "Full backup FAILED for full_$$STAMP - skipping cleanup" rm -rf "$$DEST" fi sleep 604800 done volumes: postgres_data: