Docker Volume Backup and Restore
A named volume has no export command. The answer is a throwaway container that mounts both the volume and a host directory - shown here as a full round trip: write data, back it up, destroy the volume, restore it, and prove the bytes came back.
Storage and Networking Guide 13 of 46 Intermediate
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- Shellbash
- Architectureamd64
- TimeAbout 12 min
- Reviewed20 August 2026
Tested on the versions above. Archive sizes and timestamps differ per run. Match the shape of the output, not the exact values.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- You can create and mount a named volume - guide 12 in this path.
- A host directory you can write to.
-
Why there is no docker volume export
Docker deliberately has no command to copy a volume's contents out. A volume is just a directory the daemon manages, and the supported way to reach it is from inside a container. So every backup recipe has the same shape: run a short-lived container that mounts the volume on one path and a host directory on another, then copy between them. The container is the tool, and it exists for a second.
bash Example session docker volume create cg-datacg-datadocker run --rm -v cg-data:/data alpine:3.22 sh -c "echo important-record-1 > /data/records.txt; echo important-record-2 >> /data/records.txt; ls -l /data"total 4-rw-r--r-- 1 root root 38 Aug 20 07:08 records.txtExpected resultThe volume created, then a file written into it by a container that has already exited.
Success condition
records.txtexists in the volume. The container that wrote it is gone - the data is not. -
Back it up into a tar archive
Two mounts in one container: the volume at
/data, a host directory at/backup.tarreads one and writes the other.-C /data .archives the contents of the directory rather than the directory itself, which is what makes the restore land in the right place instead of one level too deep.bash docker run --rm -v cg-data:/data -v /tmp/cg-backup:/backup alpine:3.22 tar czf /backup/cg-data.tar.gz -C /data .# no output - tar is quiet on successExpected resultAn archive on the host, listing
./records.txtat the top level.Verify it worked
bash Example session ls -lh /tmp/cg-backup-rw-r--r-- 1 root root 153 Aug 20 07:08 cg-data.tar.gztar tzf /tmp/cg-backup/cg-data.tar.gz././records.txtSuccess condition
tar tzflists your files. If it listsdata/records.txtinstead, the-Cwas missing and the restore will nest the data one directory too deep. -
Destroy the volume
A backup nobody has restored is a hypothesis, not a backup. Remove the volume outright so the restore has to be real. Note that Docker refuses this if any container - even a stopped one - still references the volume.
bash # this destroys the data permanently - the archive is now the only copydocker volume rm cg-datacg-dataExpected resultThe volume name echoed back.
Success condition
docker volume lsno longer lists cg-data. -
Restore into a fresh volume
The mirror image of the backup: mount the new volume and the host directory, then untar into the volume. Restoring into a NEW volume rather than over the original is the safer habit - it leaves you somewhere to fall back to when the restore is the thing that turns out to be broken.
bash docker volume create cg-data-restoredcg-data-restoreddocker run --rm -v cg-data-restored:/data -v /tmp/cg-backup:/backup alpine:3.22 tar xzf /backup/cg-data.tar.gz -C /data# no output - extraction succeededExpected resultBoth records read back out of the restored volume.
Verify it worked
bash docker run --rm -v cg-data-restored:/data alpine:3.22 cat /data/records.txtimportant-record-1important-record-2Success conditionThe contents match what you wrote before the volume was destroyed. That is the round trip proved, not assumed.
-
Clone a volume without touching the host
When you only need a copy - a scratch database to experiment against, say - you can skip the archive entirely and mount both volumes in one container. Note that the destination volume did not exist beforehand: naming a volume in
-vcreates it.bash docker run --rm -v cg-data-restored:/src -v cg-clone:/dst alpine:3.22 sh -c "cp -a /src/. /dst/ && cat /dst/records.txt"important-record-1important-record-2Expected resultThe copy printed, and both volumes listed.
Verify it worked
bash Example session docker volume ls --filter name=cg-DRIVER VOLUME NAMElocal cg-clonelocal cg-data-restoredSuccess condition
cg-cloneexists and holds the same records.cp -apreserves ownership and timestamps, which matters when the application checks them. -
Back up a database the right way
Everything above copies files underneath a running process, and for a database that is how you get a corrupt backup. A database that is mid-write has state in memory and in progress on disk. Use the engine's own dump tool and archive that output instead - the file copy is only safe for a volume whose writer is stopped.
bash # for a live database, dump through the engine - do NOT tar its data volumedocker exec cg-db pg_dump -U postgres appdb > backup.sql# or stop the container first, then use the tar recipe aboveExpected resultA logical dump written on the host.
Success conditionYou know which of the two methods applies. Stopped volume: tar it. Running database: dump it.
-
Clean up
Remove the volumes this guide created. Volumes outlive their containers by design, so they are the resource most likely to be left behind quietly consuming disk.
bash # permanently deletes the data in both volumesdocker volume rm cg-data-restored cg-clonecg-data-restoredcg-cloneExpected resultBoth names echoed back.
Success condition
docker volume ls --filter name=cg-returns nothing but the header.
Troubleshooting
The restored files are one directory too deep
Why: The archive was created without
-C, so it recorded thedata/prefix and the extract recreated it.Fix:Archive with
tar czf ... -C /data .so paths are relative to the volume root. Check withtar tzfbefore trusting an archive.bash tar tzf /tmp/cg-backup/cg-data.tar.gz | head -3# want ./file, not data/filevolume is in use
Why: A container still references the volume. Stopped containers count - removing a container is not the same as removing the volume it used.
Fix:Find the referencing containers and remove them first.
bash docker ps -a --filter volume=cg-data --format "{{.Names}} {{.Status}}"Permission denied writing to the host backup directory
Why: The container writes as the user its image runs as - usually root - and the host directory may not permit that, or SELinux may block the bind mount.
Fix:Point
-vat a directory you control, or set ownership on the host first. On SELinux systems add:zto the bind mount.bash docker run --rm -v cg-data:/data -v /tmp/cg-backup:/backup:z alpine:3.22 tar czf /backup/out.tar.gz -C /data .The archive is suspiciously small
Why: Usually the volume was empty at backup time - frequently because the application writes somewhere other than the path you assumed was the volume.
Fix:List the volume's contents before archiving, and confirm the container's mount destination matches where the application actually writes.
bash docker run --rm -v cg-data:/data alpine:3.22 ls -la /datadocker inspect cg-app --format "{{json .Mounts}}"