Containers are disposable. Your data isn’t. In Docker, persistent data lives in named volumes or bind mounts, and those are what you back up. Images can be pulled again, and Compose files belong in Git. This guide shows how to find your data, back it up consistently, automate it, and restore it.
Step 1: find where the data lives
docker volume ls
docker inspect <container> --format '{{json .Mounts}}' | jq
docker volume inspect <volume> --format '{{ .Mountpoint }}' # usually /var/lib/docker/volumes/<name>/_data
| Storage type | Example in Compose | Where it is |
|---|---|---|
| Named volume | pgdata:/var/lib/postgresql/data |
/var/lib/docker/volumes/<project>_pgdata/_data |
| Bind mount | ./data:/data |
Your project directory |
Bind mounts are easier to back up, because they’re ordinary directories. Named volumes work too, using the methods below. See Docker volumes.
Step 2: get consistent data
Copying files while an app writes to them can give a corrupt backup, especially for databases.
- Databases: use the database’s own dump tool while it runs. This is the safest option.
docker compose exec -T db pg_dump -U app appdb | gzip > /backups/appdb-$(date +%F).sql.gz
docker compose exec -T mysql mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction --all-databases | gzip > /backups/mysql-$(date +%F).sql.gz
- Files (uploads, configs): a copy while running is usually fine.
- Everything else (SQLite, apps with internal state): stop the container briefly, copy, and start it again:
docker compose stop app && tar czf /backups/app-$(date +%F).tgz -C /opt/app data && docker compose start app
Step 3: back up a named volume with a helper container
A temporary container mounts the volume read-only, and writes a tarball to the host:
docker run --rm \
-v myapp_data:/source:ro \
-v /backups:/backup \
alpine tar czf /backup/myapp_data-$(date +%F).tgz -C /source .
This works without knowing the volume’s path on disk, and it works the same on any host.
Restore a named volume
docker compose down # stop the app using it
docker volume create myapp_data # if it doesn't exist
docker run --rm \
-v myapp_data:/target \
-v /backups:/backup:ro \
alpine sh -c "rm -rf /target/* && tar xzf /backup/myapp_data-2026-10-01.tgz -C /target"
docker compose up -d
For databases, restore from the dump into a fresh database container instead:
gunzip -c /backups/appdb-2026-10-01.sql.gz | docker compose exec -T db psql -U app appdb
Step 4: automate and send off-site
A backup on the same server doesn’t protect you from losing that server. A simple nightly script:
#!/bin/bash
# /usr/local/bin/docker-backup.sh
set -euo pipefail
D=/backups/$(date +%F); mkdir -p "$D"
cd /opt/app
docker compose exec -T db pg_dump -U app appdb | gzip > "$D/appdb.sql.gz"
docker run --rm -v app_uploads:/s:ro -v "$D":/b alpine tar czf /b/uploads.tgz -C /s .
find /backups -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + # keep 7 days locally
Run it from cron, then push /backups off-site with restic, Borg or rclone (to object storage or a second server). Deduplicating tools like restic and Borg keep many versions cheaply, and encrypt them. See crontab, off-site backups, and storage VPS.
Tools that automate this in containers: docker-volume-backup (offen), Backrest (a web UI for restic), and the backup features in Coolify, Dokploy or Portainer Business.
Step 5: test restores
A backup you’ve never restored is a hope, not a backup. Regularly:
- Spin up a test VPS or a separate Compose project.
- Restore the volumes and database dumps.
- Start the app, and check that the data is there.
See backup strategy.
What not to bother backing up
- Images: pull them again (pin versions in Compose for reproducibility).
- Containers: recreate them from Compose.
/var/lib/dockeras a whole: big, fragile, and engine-version specific. Back up the volumes instead.
Do back up your Compose files and .env (in Git, with secrets kept out of Git).
Quick reference
| Task | Command |
|---|---|
| List volumes | docker volume ls |
| Volume path | docker volume inspect <v> --format '{{ .Mountpoint }}' |
| Back up a volume | docker run --rm -v vol:/s:ro -v /backups:/b alpine tar czf /b/vol.tgz -C /s . |
| Restore a volume | … alpine tar xzf /b/vol.tgz -C /target |
| Postgres dump | docker compose exec -T db pg_dump -U user db |
Related: Docker Compose tutorial and database backup automation.