July 12, 2026 · Docker HQ Team

How to Back Up Docker Volumes (and Restore Them)

To back up a Docker volume, run a temporary container that mounts the volume, then tar its contents to a directory on the host: docker run --rm -v myvol:/data -v $(pwd):/backup alpine tar czf /backup/myvol.tar.gz -C /data .. To restore, create the target volume and untar the archive back into it. That's the whole trick. Everything below is about how to backup Docker volumes safely when the data is live and in use.

The reason you can't just cp a volume is that Docker volumes live under /var/lib/docker/volumes/ and copying that path directly skips permission handling, misses named-volume metadata, and breaks the moment you move to a different storage driver. The container-based approach works the same on any host, any driver.

Why volume backups are their own problem

Images are reproducible. You can rebuild them from a Dockerfile or pull them again from a registry (and if you run your own, managing a private Docker registry is a related rabbit hole). Volumes are not reproducible. They hold your Postgres data, your uploaded files, your Prometheus metrics history. Lose the volume and there is no docker pull that brings it back.

So the backup target is almost always named volumes. Bind mounts are just host directories, back those up with whatever you already use for the host filesystem.

First, see what you have:

docker volume ls
docker volume inspect myvol

The inspect output shows the Mountpoint, the driver, and any labels. Note the driver. Everything here assumes the local driver, which is the default. If you're on a networked volume plugin (NFS, cloud block storage), the plugin usually has its own snapshot story and you should use that instead.

The basic command to backup Docker volumes

Here's the pattern again, broken down:

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

What each piece does:

  • --rm throws away the helper container when it exits. You don't want these piling up.
  • -v myvol:/data:ro mounts your volume read-only inside the helper. The :ro matters, it means a bug in your backup command can't corrupt the source.
  • -v "$(pwd)":/backup mounts the current host directory so the archive lands somewhere you can reach it.
  • -C /data . tars the contents of /data rather than the /data directory itself, so restore drops files back at the volume root.

Use alpine because it's tiny and has tar built in. busybox works too. Match the tar flavor if you care about extended attributes; GNU tar (debian, ubuntu images) preserves more metadata than busybox tar, which matters for things like file capabilities but rarely for app data.

The part everyone skips: stop writes first

A tar of a live database volume gives you a backup that might not restore. You're copying files while the database rewrites them, so you can catch a half-written page. This is the difference between a backup you have and a backup that works.

Two honest options.

Option A: stop the container

Simplest and always correct. Stop the service, back up, start it again.

docker stop myapp-db
docker run --rm -v pgdata:/data:ro -v "$(pwd)":/backup \
  alpine tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
docker start myapp-db

Downtime equals however long the tar takes. For a few gigabytes that's seconds. For a busy production database you probably can't accept even that, which brings us to option B.

Option B: use the app's own dump tool

For databases, a filesystem tar is the wrong tool anyway. Use the native dump, which gives you a consistent snapshot with no downtime:

# Postgres
docker exec myapp-db pg_dumpall -U postgres | gzip > pg-$(date +%F).sql.gz

# MySQL / MariaDB
docker exec myapp-db sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' | gzip > mysql-$(date +%F).sql.gz

The logical dump is portable across versions and architectures too, which a raw data directory is not. Restore a pg_dumpall into a fresh Postgres and it just works. Restore a raw pgdata tar into a different major version and it won't start. For anything with a real dump utility, prefer it. Use the tar method for volumes that hold plain files: uploads, config, static content, Grafana dashboards.

Restoring a volume

Create the volume first, then extract into it with the same helper-container trick:

docker volume create myvol-restored

docker run --rm \
  -v myvol-restored:/data \
  -v "$(pwd)":/backup \
  alpine \
  tar xzf /backup/myvol-2026-07-12.tar.gz -C /data

Then point your container at the restored volume and start it. Test the restore into a new volume name like above rather than clobbering the original. If the archive is bad, you still have the live data.

A backup you have never restored is a guess. Actually run the restore into a throwaway volume and boot the app against it at least once. The failure you find in a drill is cheap. The one you find during an outage is not.

Getting the archive off the host

The tarball on the same disk as the volume protects you from docker volume rm, not from a dead disk. Push it somewhere else. If you already SSH into the box, pull backups down with the SSH connection you have:

scp user@server:/opt/backups/myvol-2026-07-12.tar.gz ./

Or sync a whole backup directory to object storage from the host:

aws s3 sync /opt/backups s3://my-bucket/docker-backups/

Whatever you pick, the rule is the same: at least one copy on different hardware, ideally a different provider.

Doing this from your phone

SSHing into a server to run a tar command works fine from a laptop. It's less fun when the disk-full alert fires while you're out. Docker HQ connects to your hosts over SSH and lets you inspect volumes, open a shell, and run these commands from your phone, so a backup that failed overnight is something you can check and re-run without opening a laptop. It also sends push alerts when a host or container goes down, which is usually how you find out a backup job died in the first place. Manual backups are fine to start. Once you have more than a couple of hosts, scheduling automatic Docker volume backups so you're not the cron job is the next step, and you can manage that across a team from one place.

Frequently asked questions

Can I back up a Docker volume without stopping the container?

Yes, but with a caveat. For plain files (uploads, config, static assets) a live tar is fine. For databases, a live filesystem tar can capture a half-written state that won't restore cleanly. Use the database's own dump tool (pg_dumpall, mysqldump) for a consistent no-downtime backup, or stop the container briefly for a filesystem-level copy.

Where does Docker actually store volume data?

With the default local driver, under /var/lib/docker/volumes/<name>/_data on the host. You can see the exact path with docker volume inspect <name>. Don't back up that path directly, permissions and driver differences will bite you. Mount the volume into a container and tar it from there instead.

How do I move a volume to another server?

Back it up to a tarball, copy the file over with scp or rsync, then restore it into a new volume on the target host using the tar xzf command above. This works across machines and even across CPU architectures for plain-file volumes. For database volumes, use a logical dump instead of the raw data directory, since raw directories aren't portable across engine versions.

Should I compress backups with gzip?

Usually yes. tar czf uses gzip and cuts size a lot for text and config data. For volumes that are already-compressed media (video, images, encrypted blobs) compression buys little and just burns CPU, so tar cf without the z is fine there. If you have the CPU and want smaller archives, pipe through zstd instead of gzip.

Next step

Pick your single most important volume right now, the one whose loss would ruin your week, and run the stop-backup-restore cycle end to end into a throwaway volume. Not the backup, the restore. If it boots, you have a backup. If it doesn't, better to learn that today.