PostgreSQL backup and restore made easy with pg_basebackup
pg_backctl (PostgreSQL Backup Control) is a command-line tool that simplifies creating and restoring PostgreSQL physical backups using pg_basebackup. It handles the complexity of backup operations, S3 storage, WAL management, and configuration so you can focus on your data.
- 🚀 Easy Backups - Create physical backups with
pg_basebackup(gzip/bzip2 compression) - 📦 S3 Integration - Upload/download backups to/from S3-compatible storage
- 🔄 WAL Management - Automatic WAL fetching and replay for point-in-time recovery
- 🔐 Integrity Checks - SHA256 checksums for backup verification
- ⏰ Retention Policies - Auto-cleanup old backups (by count or age)
- 🐳 Docker Native - Works seamlessly with Docker Compose
- ⚙️ Config Files - INI-style configuration for easy automation
- 🎯 Multiple Modes - Override, new volume, or standby/replica modes
# 1. Copy the example config
cp backup.conf.example backup.conf
# 2. Edit backup.conf with your settings
# 3. Run backup
./create_backup.sh -c backup.conf# 1. Copy the example config
cp recovery.conf.example recovery.conf
# 2. Edit recovery.conf with your settings
# 3. Run restore
./import_db_backup.sh -c recovery.confThat's it! 🎉
- Docker and Docker Compose installed
- S3-compatible storage (AWS S3, OVH, MinIO, etc.) or local storage
- PostgreSQL database running in Docker Compose
.envfile with AWS credentials (for S3 mode):AWS_ACCESS_KEY=your_access_key AWS_SECRET_KEY=your_secret_key AWS_REGION=us-east-1
docker build -t pg_backctl:latest .Create backup.conf from the example:
[database]
service = postgres
compose_file = docker-compose.yml
user = replication_user
[destination]
s3_url = s3://mybucket
s3_endpoint = https://s3.provider.com
s3_prefix = backups
[backup]
compression = gzip
retention_count = 10See backup.conf.example for all options.
Create recovery.conf from the example:
[source]
s3_url = s3://mybucket
s3_endpoint = https://s3.provider.com
s3_search_prefix = backups/
[target]
volume_name = pgdata
service = postgres
compose_file = docker-compose.yml
[restore]
new_volume_name = pgdata-restoredSee recovery.conf.example for all options.
# Add to crontab
0 2 * * * /path/to/pg_backctl/create_backup.sh -c /path/to/backup.conf./create_backup.sh -c backup.conf -l "pre-migration-$(date +%Y%m%d)"./create_backup.sh \
-n postgres \
-f docker-compose.yml \
-P /backups/postgresEdit backup.conf:
[backup]
compression = bzip2 # Better compression, slower# Safest option - creates new volume, keeps original
./import_db_backup.sh -c recovery.confEdit recovery.conf:
[source]
s3_backup_path = backups/20251027T143000 # Specific backupThen restore:
./import_db_backup.sh -c recovery.conf./import_db_backup.sh -c recovery.conf -o./import_db_backup.sh -c recovery.conf -S./import_db_backup.sh \
-P /backups/20251027T143000 \
-v pgdata \
-n postgres \
-f docker-compose.yml \
-V pgdata-restored./import_db_backup.sh \
-c recovery.conf \
-C confs/postgresql.auto.conf \
-H confs/pg_hba.confpg_backctl automatically generates SHA256 checksums for all backup files, enabling external verification tools to validate backup integrity.
Each backup includes:
backup.sha256- SHA256 checksums for all filesbackup.sha256.info- Metadata describing the checksum format
Manual Verification:
# Download backup
aws s3 cp s3://bucket/backups/20251027T143000/ ./backup --recursive
# Verify integrity
cd backup
sha256sum -c backup.sha256Why external verification?
- Separates concerns (backup creation vs. verification)
- Allows scheduled verification independent of backup timing
- Integrates with your existing monitoring stack
- Can verify old backups for bit rot detection
Automatically cleanup old backups from S3 to save storage costs.
Keep last N backups:
[backup]
retention_count = 10 # Keep last 10 backupsKeep backups for N days:
[backup]
retention_days = 30 # Keep backups for 30 daysNote: retention_count takes precedence if both are set.
pg_backctl supports multiple S3 path structures:
# Organized by prefix (recommended)
s3_prefix = backups
# Backward compatible with other tools
s3_prefix = postgresql-cluster/base
# Root level storage
s3_prefix =Auto-detect vs. Specific Backup:
# Auto-detect: restore latest backup
s3_search_prefix = backups/
# Specific: restore exact backup
s3_backup_path = backups/20251027T143000Creates a new volume, leaving the original intact.
./import_db_backup.sh -c recovery.conf -V pgdata-restored./import_db_backup.sh -c recovery.conf -oCreates a standby/replica server.
./import_db_backup.sh -c recovery.conf -S./create_backup.sh [OPTIONS]
Options:
-c, --config FILE Load configuration from file
-n SERVICE_NAME Docker Compose service name
-f COMPOSE_FILEPATH Path to docker-compose file
-u S3_BACKUP_URL S3 backup URL (e.g., s3://mybucket)
-e S3_ENDPOINT S3 endpoint URL
-P BACKUP_PATH Local backup path (alternative to S3)
-l BACKUP_LABEL Custom backup label
-C COMPRESSION Compression: gzip, bzip2, none (default: gzip)
-U DB_USER Database user (default: postgres)
-O IMAGE pg_backctl docker image (default: pg_backctl:latest)
-h, --help Show help messagePriority: .env < config file < CLI arguments
./import_db_backup.sh [OPTIONS]
Options:
-c, --config FILE Load configuration from file
-u S3_BACKUP_URL S3 backup URL
-e S3_ENDPOINT S3 endpoint URL
-P BACKUP_PATH Local backup path
-v VOLUME_NAME Docker volume name
-n SERVICE_NAME Docker Compose service name
-f COMPOSE_FILEPATH Path to docker-compose file
-o Override volume mode (WARNING: deletes data!)
-V NEW_VOLUME_NAME New volume mode (recommended)
-S Standby mode
-C REPLACE_CONF Replace postgresql.auto.conf after restore
-H REPLACE_PG_HBA Replace pg_hba.conf after restore
-I POST_INIT_CONF Directory with post-init SQL scripts
-O IMAGE pg_backctl docker image
-h, --help Show help message- Connect to Database - Uses
pg_basebackupvia Docker Compose service - Create Physical Backup - Generates base backup + WAL files in tar format
- Compress - Apply gzip or bzip2 compression
- Generate Checksums - Create SHA256 manifest (backup.sha256)
- Upload to S3 - Transfer all files to S3-compatible storage
- Apply Retention - Cleanup old backups based on retention policy
- Log Results - JSON logs for monitoring (New Relic, etc.)
Backup Structure:
s3://bucket/backups/20251027T143000/
├── base.tar.gz # Base backup
├── pg_wal.tar.gz # WAL files
├── backup_manifest # PostgreSQL manifest
├── backup.sha256 # SHA256 checksums
└── backup.sha256.info # Checksum metadata
- Download Backup - Fetch backup files from S3 or use local path
- Clean Volume - Wipe
/datadirectory contents - Restore Base Backup - Extract base.tar.gz to
/data - Restore WAL Files - Extract pg_wal.tar.gz to
/data/pg_wal - Create recovery.signal - Tell PostgreSQL to perform recovery
- Start Database - Launch PostgreSQL container
- Replace Configs - Apply custom postgresql.auto.conf / pg_hba.conf (optional)
- Run Post-Init Scripts - Execute SQL scripts (optional)
PostgreSQL Versions: 12, 13, 14, 15, 16, 17+
Backup Formats Supported:
- pg_backctl format:
base.tar.gz+pg_wal.tar.gz - Legacy format:
data.tar.bz2(backward compatible)
S3 Providers:
- AWS S3
- OVH Cloud Storage
- DigitalOcean Spaces
- MinIO
- Any S3-compatible storage
Cause: S3 path mismatch or wrong prefix
Solution:
- Check
s3_search_prefixin recovery.conf - List bucket contents:
aws s3 ls s3://bucket/ --endpoint-url $endpoint - Use
/to search root level
Cause: Not enough free disk space for backup
Solution:
- Check disk space:
df -h /tmp - Increase
min_disk_space_gbthreshold or free up space - For S3 backups, ensure /tmp has enough space
Cause: AWS credentials, network, or endpoint issue
Solution:
- Verify credentials in
.env - Test S3 connection:
aws s3 ls s3://bucket --endpoint-url $endpoint - Check S3 endpoint URL is correct
Cause: Files missing or corrupted during upload
Solution:
- Retry backup
- Check network stability
- Verify S3 endpoint reliability
| Code | Description | Common Cause |
|---|---|---|
| 10 | Missing command | Docker or required tool not installed |
| 11 | Missing env variable | AWS credentials not set |
| 12 | Missing argument | Required CLI argument not provided |
| 14 | Usage error | Invalid argument combination |
| 15 | Backup failed | Backup or upload operation failed |
| 16 | Insufficient disk space | Not enough free space |
Q: Can I backup multiple databases?
A: Yes, pg_basebackup backs up the entire PostgreSQL cluster (all databases).
Q: How long do backups take? A: Depends on database size. For 10GB: ~5-10 minutes with gzip.
Q: Can I restore to a different PostgreSQL version? A: Only within the same major version (e.g., 15.x to 15.y is OK, 15.x to 16.x is not).
Q: What's the difference between gzip and bzip2? A: gzip is faster, bzip2 has better compression. For most cases, use gzip.
Q: How do I test my backups? A: Use the new volume mode (-V) to restore to a test volume periodically.
pg_backctl uses structured JSON logging for easy integration with monitoring stacks (New Relic, Datadog, ELK, etc.).
Log Location: logs/pg_backctl.log (with automatic rotation)
Format:
Standard JSON with level and message fields, plus custom context.
{"level":"INFO","message":"Backup completed","timestamp":"2025-12-15T10:15:08Z","event":"backup.success","duration":450}Features:
- Standardized: Always includes
level,message,timestamp. - Context-Rich: Custom key-value pairs (e.g.,
event=backup.fail,size_gb=10) are automatically parsed into JSON fields. - Unified: All scripts write to the same log file.
For easier container identification in docker ps and monitoring tools, customize container names in your config:
[docker]
container_name = pg_backup_prod # Default: pg_backctl
standby_container_name = pg_replica_01 # Default: {container_name}_standbyContributions are welcome! Please feel free to submit a Pull Request.
- Clone the repository
- Make your changes
- Test thoroughly
- Submit a PR with a clear description
Please use GitHub Issues to report bugs or request features.
Include:
- pg_backctl version
- PostgreSQL version
- Error messages and logs
- Steps to reproduce
PostgreSQL License - See LICENSE file for details
Made with ❤️ by the Avisto Telecom team
For issues and feature requests, please visit: https://github.com/AvistoTelecom/pg_backctl/issues