Intro
Backups are only useful when you can restore them quickly and repeatably. This guide shows modern, tested ways to back up and restore Docker Compose applications. You will learn exactly what to protect, how to capture data from common services (PostgreSQL, Redis, Nginx), how to validate restores, and how to plan for disaster recovery and rollback. All commands are safe to run on a developer laptop or a small production host.
Tip: run docker compose config to view the effective configuration that Compose will use. That expanded view helps you audit volumes, environment variables, and bind mounts.
What to back up
Back up everything that carries state or defines how the stack runs:
- Compose files:
docker-compose.yml, anycompose.*.ymloverrides, and.env. - Service configs: Nginx configuration, application config files, TLS certificates and keys.
- Named volumes: databases and any service state (for example,
pgdata,redisdata). - Bind‑mounted data directories: application uploads, static content.
- Secrets and credentials: store them securely (not inside images) and include a safe export in your backup process.
- Version info: image tags (and optionally digests) and plugin versions.
Reference stack
Use this minimal stack to follow along. Adjust names to your project.
version: "3.9"
services:
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes\n volumes:
- redisdata:/data
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./site:/usr/share/nginx/html:ro
volumes:
pgdata:
redisdata:
Create a place for backups and a timestamp variable:
mkdir -p backups
TS=$(date -u +%Y%m%d_%H%M%S)
Backup workflow (tested)
A simple, repeatable workflow reduces mistakes:
- Inventory: list state (volumes, bind mounts, databases, app configs, secrets).
- Quiesce when needed: pause writes or use service‑native dump tools.
- Backup: capture data and configs to a timestamped archive.
- Store: save to durable, off‑host storage.
- Verify: checksum and test‑restore in a throwaway environment.
- Automate: script and schedule.
- Rehearse: perform a full restore regularly.
Back up Compose files and bind‑mounted content
# Compose files and env
tar czf backups/compose_${TS}.tgz docker-compose.yml .env
# Nginx config and site content (bind mounts)
tar czf backups/nginx_${TS}.tgz nginx/conf.d site
PostgreSQL: prefer logical dumps
Logical dumps are consistent while the DB is online and portable across hosts.
- Custom‑format dump (recommended: faster restore, parallelizable):
# Writes compressed custom‑format dump to stdout
PGPASSWORD=secret docker compose exec -T db \
pg_dump -U app -d app -Fc -Z 6 > backups/pg_app_${TS}.dump
- Plain SQL dump (readable, slower to restore on large DBs):
PGPASSWORD=secret docker compose exec -T db \
pg_dump -U app -d app | gzip > backups/pg_app_${TS}.sql.gz
- Also snapshot cluster roles and global objects (optional, useful for multi‑DB setups):
PGPASSWORD=secret docker compose exec -T db \
pg_dumpall -U app --globals-only > backups/pg_globals_${TS}.sql
- Only if you must: raw data directory snapshot. Stop writes and match the same Postgres major version on restore.
# Quiesce: ensure no writers or stop the db service first
# docker compose stop app-writers ...
docker compose stop db
# Archive the data directory from a one‑off container with the same image
docker compose run --rm -T db sh -c 'tar czf - /var/lib/postgresql/data' > backups/pgdata_${TS}.tgz
docker compose up -d db
Redis: capture AOF/RDB safely
If AOF is enabled (as in this stack), request a rewrite for a compact, consistent file; otherwise trigger a background snapshot.
# If using AOF: rewrite append‑only file to a consistent state
docker compose exec -T redis redis-cli BGREWRITEAOF
# If using RDB snapshots instead: create one in the background
# docker compose exec -T redis redis-cli BGSAVE
# Archive the data directory (contains AOF or RDB)
docker compose exec -T redis sh -c 'tar czf - /data' > backups/redis_${TS}.tgz
Optional: checksums and encryption
( cd backups && sha256sum * > SHA256SUMS_${TS}.txt )
# Optionally encrypt archives before off‑host transfer (example with age or gpg)
# age -r <recipient> -o backups/pg_app_${TS}.dump.age backups/pg_app_${TS}.dump
Off‑host copy
Move backups off the host and off the primary region if possible. Use an object store with lifecycle policies (for example, keep daily for 14 days, weekly for 8 weeks, monthly for 12 months). Ensure transport encryption and access controls.
Restore workflow (tested)
Always restore into a disposable environment first. Never overwrite production until you validate.
Stage configs and start core services
# In a clean working dir
mkdir -p nginx/conf.d site
tar xzf backups/compose_${TS}.tgz
tar xzf backups/nginx_${TS}.tgz -C .
docker compose up -d db redis nginx
Restore PostgreSQL
- From custom‑format dump (fast, recommended):
# Create database if not present
PGPASSWORD=secret docker compose exec -T db psql -U app -c 'CREATE DATABASE app;' || true
# Restore using pg_restore (can add -j for parallelism if db is big)
cat backups/pg_app_${TS}.dump | \
docker compose exec -T db pg_restore -U app -d app --no-owner --no-privileges
- From plain SQL dump:
gunzip -c backups/pg_app_${TS}.sql.gz | \
docker compose exec -T db psql -U app -d app
- From raw data directory snapshot (must match Postgres major version):
docker compose stop db
# Clear the data directory
docker compose run --rm -T db sh -c 'rm -rf /var/lib/postgresql/data/*'
# Extract to absolute paths preserved in the tar
cat backups/pgdata_${TS}.tgz | \
docker compose run --rm -T db sh -c 'tar xzf - -C /'
docker compose up -d db
If you saved global objects (roles, extensions), apply them before restoring databases:
[ -f backups/pg_globals_${TS}.sql ] && \
docker compose exec -T db psql -U app -f /dev/stdin < backups/pg_globals_${TS}.sql || true
Restore Redis
docker compose stop redis
# Clear and restore data directory
cat backups/redis_${TS}.tgz | \
docker compose run --rm -T redis sh -c 'rm -rf /data/* && tar xzf - -C /'
docker compose up -d redis
Reload Nginx if configs changed
docker compose exec -T nginx nginx -t && \
docker compose exec -T nginx nginx -s reload
Note: Project‑scoped volume names may include a prefix. Running exec or run in the service avoids guessing the volume name because the container already mounts the right volume.
Validation checklist
Validate before serving traffic:
- Containers healthy:
docker compose ps
docker compose logs --since=2m db redis nginx
- PostgreSQL sanity:
docker compose exec -T db psql -U app -d app -c \
"select count(*) as tables from information_schema.tables where table_schema='public';"
- Redis sanity:
docker compose exec -T redis redis-cli ping
docker compose exec -T redis redis-cli dbsize
- Nginx responds:
curl -I http://localhost:8080/
- Data spot checks: known rows, files, and user accounts exist.
- Checksums: if you stored
SHA256SUMS, verify them.
( cd backups && sha256sum -c SHA256SUMS_*.txt )
- Access and permissions: the app can read/write where needed.
Disaster recovery and rollback
Plan for the worst so recovery is calm and fast:
- RPO (recovery point objective): how much data loss is acceptable. Set backup frequency accordingly.
- RTO (recovery time objective): how quickly you must be back online. Pre‑pull images and keep scripts ready.
- Storage: maintain encrypted, off‑host copies; for critical systems, store off‑region.
- Version pinning: pin image tags and avoid major‑version drift across DB restores.
- Rollback: keep at least one previous snapshot ready. If a change fails, restore the last known good backup and restart services.
- Staged restore: bring up a parallel stack with a different project name so you do not clobber production volumes.
export COMPOSE_PROJECT_NAME=stack_restore_test
docker compose up -d
- Runbook: document exact commands, file locations, and success criteria. Practice on a schedule.
Automate and schedule
Start with small, reliable scripts. Example skeletons:
# backup.sh
set -euo pipefail
TS=$(date -u +%Y%m%d_%H%M%S)
mkdir -p backups
tar czf backups/compose_${TS}.tgz docker-compose.yml .env
PGPASSWORD=secret docker compose exec -T db pg_dump -U app -d app -Fc -Z 6 > backups/pg_app_${TS}.dump
docker compose exec -T redis redis-cli BGREWRITEAOF || docker compose exec -T redis redis-cli BGSAVE
docker compose exec -T redis sh -c 'tar czf - /data' > backups/redis_${TS}.tgz
tar czf backups/nginx_${TS}.tgz nginx/conf.d site
( cd backups && sha256sum * > SHA256SUMS_${TS}.txt )
# restore.sh
set -euo pipefail
TS="$1" # pass the timestamp to restore, e.g., 20250101_120000
tar xzf backups/compose_${TS}.tgz
mkdir -p nginx/conf.d site
tar xzf backups/nginx_${TS}.tgz -C .
docker compose up -d db redis nginx
# Postgres
PGPASSWORD=secret docker compose exec -T db psql -U app -c 'CREATE DATABASE app;' || true
cat backups/pg_app_${TS}.dump | docker compose exec -T db pg_restore -U app -d app --no-owner --no-privileges
# Redis
docker compose stop redis
cat backups/redis_${TS}.tgz | docker compose run --rm -T redis sh -c 'rm -rf /data/* && tar xzf - -C /'
docker compose up -d redis
Schedule with your system scheduler (cron, systemd timers, or a CI job), and push archives off‑host after creation.
Common mistakes (and fixes)
- Backing up containers instead of data: back up volumes, bind mounts, and logical dumps.
- Filesystem tars of live databases: use service‑native logical dumps unless the DB is stopped.
- Missing
.envand configs: restores then differ from expected. - Version drift: restoring a Postgres 16 data directory into Postgres 15 (or vice versa) will fail.
- Restoring into the wrong project: set
COMPOSE_PROJECT_NAMEexplicitly to avoid unwanted new volumes. - Ignoring exit codes: use
set -euo pipefailand validate checksums. - No retention policy: define rotation so old backups do not overwrite new ones or fill storage.
- Leaving services writing during backup: quiesce writers or use native dump tools.
Conclusion
A reliable Docker Compose backup is a documented, automated process you can restore on demand. Protect configs and environment, use service‑native tools for consistent data, archive volumes and bind mounts, and validate restores with concrete checks. Start with a small pilot, measure success, and automate once you trust the steps. Pin versions, store copies off‑host, and rehearse full restores so you can execute them calmly when it matters most.