Backup your Postgres db to Digital Ocean Spaces
2.7K
PG Rocket is a Dockerized PostGIS/PostgreSQL stack with:
pgBackRest full backups to S3-compatible object storagePGDATADockerfile: custom PostGIS image with pgbackrest, cron, jq, curlpg-rocket-entrypoint.sh: startup orchestrator (config, cron, preflight, post-start setup)backup.sh: scheduled/manual full backup runner with retries + Telegramrestore.sh: interactive restore command with console detail logsdocker-compose.yml: local service definition and volume mountseed.sql: optional init seed scriptapi.telegram.org) if notifications are enabled.env from the sample file:cp sample.env .env
.env (S3 credentials, Telegram, database password, etc.).docker compose up --build
docker compose logs -f postgres
docker compose exec postgres /usr/local/bin/backup.sh
The container reads variables from .env via Compose.
ENABLE_DB_BACKUP=true)| Variable | Description |
|---|---|
POSTGRES_DB | Database name |
POSTGRES_USER | Database user |
POSTGRES_PASSWORD | Database password |
STACK_NAME | Logical stack name used in backup repo path |
S3_ENDPOINT | S3-compatible endpoint (for example Wasabi endpoint) |
S3_BUCKET | Bucket name |
S3_KEY | Access key |
S3_SECRET | Secret key |
TELEGRAM_BOT_TOKEN | Telegram bot token used by backup notifications |
TELEGRAM_CHAT_ID | Telegram target chat/channel ID |
| Variable | Default | Description |
|---|---|---|
ENABLE_DB_BACKUP | false | Enable/disable backup feature block |
S3_REGION | us-east-1 | S3 region |
BACKUP_CRON | 0 3 * * 2 | Cron schedule for backup.sh |
BACKUP_RETAIN_COUNT | 1 | repo1-retention-full |
MAX_RETRIES | 5 | Backup retry attempts |
RETRY_SLEEP_SECONDS | 60 | Delay between backup retries |
PRIMARY_READY_TIMEOUT_SECONDS | 300 | Max wait for primary mode before post-start setup is skipped |
PGBACKREST_LOCK_PATH | /tmp/pgbackrest | pgBackRest lock path |
PG_AUTO_INIT | unset | If unset and PGDATA is empty, entrypoint waits for manual restore |
.envENABLE_DB_BACKUP=true
STACK_NAME=rocket-local
POSTGRES_DB=testdb
POSTGRES_USER=testuser
POSTGRES_PASSWORD=replace-me
S3_ENDPOINT=s3.us-east-1.wasabisys.com
S3_BUCKET=replace-me
S3_KEY=replace-me
S3_SECRET=replace-me
S3_REGION=us-east-1
TELEGRAM_BOT_TOKEN=replace-me
TELEGRAM_CHAT_ID=replace-me
BACKUP_CRON=0 3 * * 2
BACKUP_RETAIN_COUNT=1
MAX_RETRIES=5
RETRY_SLEEP_SECONDS=60
PRIMARY_READY_TIMEOUT_SECONDS=300
PGBACKREST_LOCK_PATH=/tmp/pgbackrest
Current docker-compose.yml includes:
PG_AUTO_INIT: "1" (auto-init behavior enabled)./seed.sql mounted to /docker-entrypoint-initdb.d/20-seed.sqlIf you want strict manual-restore-first startup, remove PG_AUTO_INIT from compose.
pg-rocket-entrypoint.sh does the following:
PGDATA directory ownership/permissions for postgres./etc/pgbackrest/pgbackrest.conf./etc/pg-rocket-env.sh.BACKUP_CRON root /usr/local/bin/backup.shPGDATA is empty and PG_AUTO_INIT is unset, waits for manual restore.docker-entrypoint.sh postgres.pg_is_in_recovery() is false (primary mode)stanza-create (as postgres)backup.sh:
/tmp/pg-rocket-backup.lock to prevent overlap.pgbackrest --stanza=main backup --type=full.MAX_RETRIES./var/log/pgbackrest/backup_YYYY-MM-DD_HHMMSS.log.docker compose exec postgres /usr/local/bin/backup.sh
docker compose exec postgres gosu postgres pgbackrest --stanza=main info
restore.sh:
pgbackrest info --output=json.PGDATA permissions before and after restore.pgbackrest --stanza=main --log-level-console=detail --log-level-file=off restore --set="<label>" --delta --link-all
docker compose exec postgres /usr/local/bin/restore.sh
docker compose logs -f postgres
/var/log/pgbackrest/cron.logRestore detail output now prints directly to your terminal.
After restore, these messages are normal:
database system is starting upnot yet accepting connectionsstarting archive recoveryconsistent recovery state reachedarchive recovery completeDuring this window PostgreSQL may be read-only until recovery completes.
mkdir: cannot create directory '/var/lib/postgresql/18': Permission deniedCause: ownership/permission mismatch on PGDATA path.
Fix (inside container as root):
chown -R postgres:postgres /var/lib/postgresql/18 && chmod 755 /var/lib/postgresql && chmod 700 /var/lib/postgresql/18 /var/lib/postgresql/18/docker
The current scripts already enforce this on startup and restore.
Found orphan containers (...)Compose detected old services from earlier config.
Use:
docker compose up --build --remove-orphans
/tmp/pgbackrestThe stack now sets lock path and ownership explicitly. If needed:
docker compose exec postgres bash -lc 'mkdir -p /tmp/pgbackrest && chown -R postgres:postgres /tmp/pgbackrest'
Full backup duration includes checkpoint and object-storage overhead, not only logical row size. This stack currently runs full backups (--type=full) by design.
.env.Build and start:
docker compose up --build
Stop:
docker compose down
Stop and remove volumes (destructive):
docker compose down -v
Open a shell in container:
docker compose exec postgres bash
Content type
Image
Digest
sha256:134a68144…
Size
235.7 MB
Last updated
about 1 month ago
docker pull rohittp0/pg-rocket