Docker data backup cheatsheet.

Postgres

# Single DB
docker exec db pg_dump -U postgres myapp | gzip > backup-$(date +%F).sql.gz

# All databases
docker exec db pg_dumpall -U postgres | gzip > all-$(date +%F).sql.gz

# Custom format (parallel restore possible)
docker exec db pg_dump -U postgres -Fc myapp > backup-$(date +%F).dump

Postgres restore

gunzip -c backup.sql.gz | docker exec -i db psql -U postgres myapp

# Custom format
docker cp backup.dump db:/tmp/
docker exec db pg_restore -U postgres -d myapp /tmp/backup.dump

MySQL

docker exec db mysqldump --all-databases -uroot -p$PASS | gzip > backup.sql.gz

# Restore
gunzip -c backup.sql.gz | docker exec -i db mysql -uroot -p$PASS

Mongo

docker exec mongo mongodump --archive --gzip > backup-$(date +%F).archive.gz

# Restore
docker exec -i mongo mongorestore --archive --gzip < backup.archive.gz

Redis

# RDB snapshot
docker exec redis redis-cli BGSAVE
docker cp redis:/data/dump.rdb backup-$(date +%F).rdb

For AOF: copy appendonly.aof.

Named volume backup

docker run --rm \
  -v pg_data:/data \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/pg_data-$(date +%F).tar.gz -C /data .

Restore volume

# First create empty volume
docker volume create pg_data

# Extract into it
docker run --rm \
  -v pg_data:/data \
  -v $(pwd):/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/pg_data-2026-01-15.tar.gz"

Stop services before volume backup

docker compose stop web worker
docker compose stop db          # if going for consistent snapshot
# backup volume
docker compose start

Or use DB-native dump (no stop needed).

Cron backup script

#!/bin/bash
# /usr/local/bin/backup.sh
set -e

BACKUP_DIR=/srv/backups
DATE=$(date +%F-%H%M)
RETENTION_DAYS=14

mkdir -p $BACKUP_DIR

# Postgres
docker exec db pg_dump -U postgres myapp | gzip > $BACKUP_DIR/db-$DATE.sql.gz

# Uploads volume
docker run --rm -v uploads:/data -v $BACKUP_DIR:/backup alpine \
  tar czf /backup/uploads-$DATE.tar.gz -C /data .

# Prune old
find $BACKUP_DIR -name "*.gz" -mtime +$RETENTION_DAYS -delete

# Upload to S3
aws s3 sync $BACKUP_DIR s3://my-bucket/backups/
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

Upload to S3 in same container

docker run --rm \
  -v pg_data:/data \
  -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
  amazon/aws-cli \
  s3 cp s3://bucket/path /data/restore.tar.gz

restic / borgbackup

docker run --rm \
  -v $(pwd):/data \
  -e RESTIC_REPOSITORY=s3:s3.amazonaws.com/bucket/backup \
  -e RESTIC_PASSWORD=xxx \
  -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
  restic/restic \
  backup /data

Deduplication + encryption + versioning.

Backup verification

# Periodically: try restoring to a test container
docker run --rm -e POSTGRES_PASSWORD=x -v $(pwd):/backup -d --name test postgres:16
sleep 5
gunzip -c /backup/db.sql.gz | docker exec -i test psql -U postgres
# Verify data
docker exec test psql -U postgres -c "SELECT count(*) FROM users"
docker rm -f test

Untested backups are not backups.

Snapshots (cloud volumes)

If your volumes are on EBS / GCP PD: use cloud snapshots — instant, copy-on-write, no I/O on source.

Point-in-time recovery (Postgres)

WAL archiving:

db:
  image: postgres:16
  command:
    - "postgres"
    - "-c"
    - "archive_mode=on"
    - "-c"
    - "archive_command=cp %p /backup/wal/%f"
  volumes:
    - pg_data:/var/lib/postgresql/data
    - wal_backup:/backup/wal

Plus periodic base backup.

DB replication (HA, not backup)

Postgres streaming replication via replica containers. Provides failover but isn’t backup — replicates corruption too.

Encrypt backups

docker exec db pg_dump -U postgres myapp \
  | gzip \
  | openssl enc -aes-256-cbc -salt -pass file:./.backup-key \
  > backup.sql.gz.enc

# Restore
openssl dec -d -aes-256-cbc -pass file:./.backup-key < backup.sql.gz.enc \
  | gunzip \
  | docker exec -i db psql -U postgres myapp

Backup label / tagging

docker run --rm \
  -v pg_data:/data \
  -v $(pwd):/backup \
  --label backup=true \
  --label backup-date=$(date +%F) \
  alpine tar czf /backup/data.tar.gz -C /data .

Common mistakes

  • Backup script never tested (restore fails when needed).
  • Live volume copy on mounted DB (inconsistent).
  • Backups only on same disk (host failure = all lost).
  • No retention → disk fills with old backups.
  • Plain text backup → leaked DB == leaked everything.

Read this next

If you want my backup + restore + verify scripts, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .