How to Schedule Automatic Docker Volume Backups
To schedule Docker backups, write a shell script that spins up a temporary alpine container mounting each named volume, tars its contents to a timestamped file, and drop that script into a nightly cron job. That is the whole trick. A docker run --rm -v <volume>:/data alpine tar command plus a crontab entry gets you automated, restorable backups without any extra tooling.
The rest of this post is the detail that keeps those backups working when you actually need them: consistency, retention, offsite copies, and knowing when a run silently failed.
Why you back up the volume, not the container
Containers are disposable. You can rebuild one from an image in seconds. The data your app cares about lives in a volume, whether that is a Postgres data directory, uploaded files, or a Redis dump. So the backup target is the volume, and the safest way to read a volume's contents is through a container that mounts it.
First, find what you are actually storing:
docker volume ls
docker volume inspect pg_data
The Mountpoint in the inspect output lives under /var/lib/docker/volumes/. You could tar that path directly on the host, but do not. Permissions, live writes, and Docker's own metadata make it fragile. Mount the volume into a helper container instead and let Docker hand you a clean view.
The one-volume backup command
Here is the core command. It mounts pg_data read-only, mounts a host directory for output, and tars the volume into it:
docker run --rm \
-v pg_data:/data:ro \
-v /opt/backups:/backup \
alpine \
tar czf /backup/pg_data-$(date +%Y%m%d-%H%M%S).tar.gz -C /data .
The --rm throws the helper container away after it exits. The :ro flag means the backup process cannot corrupt your data even if something goes wrong. -C /data . tars the contents of the volume rather than the /data directory itself, which makes restores cleaner.
Restoring is the same idea in reverse:
docker run --rm \
-v pg_data:/data \
-v /opt/backups:/backup \
alpine \
sh -c "rm -rf /data/* && tar xzf /backup/pg_data-20260712-030000.tar.gz -C /data"
Test that restore path at least once before you rely on it. A backup you have never restored is a guess, not a backup.
Consistency: stop the writer or use a native dump
A plain tar of a live database volume can capture a half-written file. For files that change slowly this rarely bites you. For a busy database it will, eventually, at the worst possible time.
Two honest options:
Quiesce the container. Stop it, back up, start it again. Fine for a personal project, not for anything with uptime expectations.
docker stop myapp
# run the tar backup here
docker start myapp
Use the database's own dump tool. This is the correct answer for Postgres, MySQL, and friends, because it produces a transactionally consistent snapshot while the database keeps serving traffic:
docker exec pg_container \
pg_dump -U postgres mydb | gzip > /opt/backups/mydb-$(date +%Y%m%d).sql.gz
So the rule of thumb: application data and static files get the volume tar, live databases get a native dump. You can schedule both the same way.
The backup script
Roll it into one script so cron has a single thing to call. This backs up several named volumes, writes a log line, and prunes anything older than 14 days.
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/opt/backups
RETENTION_DAYS=14
VOLUMES=("pg_data" "app_uploads" "redis_data")
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
for vol in "${VOLUMES[@]}"; do
echo "[$(date -Is)] backing up $vol"
docker run --rm \
-v "$vol":/data:ro \
-v "$BACKUP_DIR":/backup \
alpine \
tar czf "/backup/${vol}-${TS}.tar.gz" -C /data .
done
# prune old backups
find "$BACKUP_DIR" -name '*.tar.gz' -mtime +"$RETENTION_DAYS" -delete
echo "[$(date -Is)] backup complete"
set -euo pipefail matters here. Without it, a failing docker run in the middle of the loop gets ignored and the script reports success while skipping a volume. With it, the script stops on the first error and exits non-zero, which is exactly what you want cron to notice.
Make it executable and give it a dry run:
chmod +x /opt/backup-volumes.sh
/opt/backup-volumes.sh
Schedule Docker backups with cron
Now the actual scheduling. Open the crontab for the user that can run Docker:
crontab -e
Add a line to run at 3am every day and append output to a log:
0 3 * * * /opt/backup-volumes.sh >> /var/log/docker-backup.log 2>&1
The 2>&1 folds errors into the same log so you have one place to look. Confirm the entry is registered with crontab -l.
If you are on a systemd host and want something more visible than cron, a systemd timer does the same job with better logging through journalctl:
# /etc/systemd/system/docker-backup.timer
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true is a real advantage over cron: if the machine was asleep or off at 3am, the timer fires as soon as it comes back instead of silently skipping the run. Pair it with a matching .service unit that calls the script, then systemctl enable --now docker-backup.timer.
The part everyone skips: knowing it failed
A scheduled backup that stops working is worse than no backup, because you think you are covered. Cron does not tell you when a job fails unless you make it. Check your log the morning after you set this up, then set up an actual alert.
The cheapest alert is a dead-man's-switch: your script pings a URL on success, and a monitoring service pages you if that ping does not arrive on schedule. Add one line to the end of the script:
curl -fsS https://your-monitor.example/ping/backup-ok > /dev/null
If the backup fails, set -e kills the script before that line runs, the ping never fires, and you get notified.
This is also where managing servers from your phone pays off. Docker HQ connects to your host over SSH and pushes an alert when a container or the host itself goes down, so a failed database container the night before a backup does not go unnoticed until morning. You can check the host's disk usage from your phone too, which matters because the most common backup failure is not a bug, it is /opt/backups filling the disk. If you want that kind of monitoring across several hosts with team access, the pricing page lays out the options.
For a deeper look at watching container health over time, see Docker Container Monitoring: A Complete Guide.
Get the backups off the box
A backup sitting on the same disk as the data protects you from a bad deploy or a fat-fingered docker volume rm. It does not protect you from a dead disk or a compromised host. Copy the archives somewhere else.
For object storage, the AWS CLI in a one-liner appended to the script works:
aws s3 sync /opt/backups s3://my-bucket/docker-backups/ --delete
For a second server, rsync over SSH is fine:
rsync -az --delete /opt/backups/ backup@host2:/opt/backups/
Whatever you pick, apply retention on the remote side too, or the offsite copy grows forever. And if the backups contain anything sensitive, encrypt them before they leave the host. Piping the tar through gpg or openssl before upload is enough for most cases.
Frequently asked questions
How often should I schedule Docker volume backups?
Match the interval to how much data you can afford to lose. Daily at a quiet hour covers most apps. For a database with frequent writes, run a native dump every few hours and keep the full volume tar nightly. There is no reason to back up a volume more often than its contents actually change.
Can I back up a volume without stopping the container?
Yes, and for databases you should use the engine's dump tool (pg_dump, mysqldump) while the container keeps running, since it gives a consistent snapshot. For static files and slowly-changing data, a live tar with :ro is usually safe. Only stop the container when the data is actively written and you have no native dump option.
Where should backup files be stored?
Write them to a host directory first, then copy offsite to object storage or a second server. Keeping the only copy on the same disk as the source defeats the purpose. Always keep at least one copy on separate hardware, and encrypt anything sensitive before it leaves the machine.
How do I restore from a backup?
Mount the target volume into a helper container, clear it, and extract the tar into it. Test this on a throwaway volume before you ever need it in an incident. The related guides on managing a private Docker registry and Docker networking basics are worth a read if your restore involves pulling images or reconnecting containers to their networks.
Next step
Set the cron job tonight, then delete a test volume tomorrow and restore it from the archive. If the restore works, you have real backups. If it does not, better to find out now than at 3am with production down.