PostgreSQL backup and restore tool using Cloudflare R2 storage, designed for Docker Compose deployments.
- 🗄️ Dump & gzip-compress your Postgres database via
docker compose exec - ☁️ Upload/download to Cloudflare R2 (S3-compatible) using AWS SDK v3
- 🔄 Restore with automatic schema reset and service restart
- 🧹 Automatic retention cleanup (configurable, default 30 days)
- 📦 Works as a CLI tool or as a Node.js module
- 🔑 Supports explicit R2 credentials or falls back to AWS CLI credential chain
# Global install (recommended for cron use)
npm install -g pg-r2-backup
# Or as a project dependency
npm install pg-r2-backup- Create a
.envfile in your project root (or export variables in your shell):
R2_ACCOUNT_ID=your_cloudflare_account_id
R2_ACCESS_KEY_ID=your_r2_access_key
R2_SECRET_ACCESS_KEY=your_r2_secret_key
R2_BUCKET=kaamhubs-backups
DB_USER=db_user
DB_NAME=schooldb
POSTGRES_SERVICE=postgres
APP_SERVICE=web-service- Run a backup:
pg-r2-backup backup- List backups:
pg-r2-backup list- Restore:
pg-r2-backup restore db_20260224_020000.sql.gzpg-r2-backup <command> [options]
Commands:
backup Dump database and upload to R2
list List local and R2 backups
restore <file> Restore from a backup (downloads from R2 if not local)
cleanup Delete R2 backups older than RETENTION_DAYS
help Show help
Options:
--env-file, -e Path to .env file (default: .env in cwd)
| Variable | Default | Description |
|---|---|---|
R2_ACCOUNT_ID |
(required) | Cloudflare account ID |
R2_ACCESS_KEY_ID |
(optional) | R2 API token access key |
R2_SECRET_ACCESS_KEY |
(optional) | R2 API token secret key |
R2_BUCKET |
school-website-backups |
R2 bucket name |
DB_USER |
db_user |
PostgreSQL user |
DB_NAME |
schooldb |
PostgreSQL database name |
POSTGRES_SERVICE |
postgres |
Docker Compose DB service name |
APP_SERVICE |
web-service |
Docker Compose app service to stop/start |
LOCAL_BACKUP_DIR |
./backups |
Local directory for backup files |
RETENTION_DAYS |
30 |
Days to retain R2 backups |
If R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY are not set, the AWS SDK credential chain is used (environment variables, ~/.aws/credentials, instance metadata, etc.).
Add a daily 2 AM backup to your crontab:
0 2 * * * cd /path/to/project && npx pg-r2-backup backup >> /var/log/pg-r2-backup.log 2>&1const { loadConfig, validateConfig, backup, list, restore, cleanupR2 } = require('pg-r2-backup');
const config = validateConfig(loadConfig()); // loads .env automatically
// Backup
await backup(config);
// List
await list(config);
// Restore
await restore(config, 'db_20260224_020000.sql.gz');
// Cleanup old R2 backups
await cleanupR2(config);- Runs
docker compose exec -T <POSTGRES_SERVICE> pg_dump -U <user> <db>and pipes output through Node'szlib.createGzip()directly to a local.sql.gzfile. - Uploads the file using
PutObjectCommand(Content-Length based, avoids chunked Transfer-Encoding which R2 doesn't fully support). - Prunes local backups older than 7 days.
- Deletes R2 backups older than
RETENTION_DAYS.
- Downloads the backup from R2 if it's not already local.
- Stops the
<APP_SERVICE>viadocker compose stop. - Resets the public schema (
DROP SCHEMA public CASCADE+ recreate). - Pipes the gunzipped dump into
docker compose exec -T <POSTGRES_SERVICE> psql. - Starts the
<APP_SERVICE>again.
- Node.js 18+
- Docker & Docker Compose installed and available in
PATH - AWS CLI not required (uses AWS SDK v3 directly)
MIT