CertGrid CertGrid
Hands-on Lab·Docker

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

Tested on the versions above. Archive sizes and timestamps differ per run. Match the shape of the output, not the exact values.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. 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.txt

    Expected resultThe volume created, then a file written into it by a container that has already exited.

    Success conditionrecords.txt exists in the volume. The container that wrote it is gone - the data is not.

  2. Back it up into a tar archive

    Two mounts in one container: the volume at /data, a host directory at /backup. tar reads 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 success

    Expected resultAn archive on the host, listing ./records.txt at 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.txt

    Success conditiontar tzf lists your files. If it lists data/records.txt instead, the -C was missing and the restore will nest the data one directory too deep.

  3. 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-data

    Expected resultThe volume name echoed back.

    Success conditiondocker volume ls no longer lists cg-data.

  4. 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 succeeded

    Expected 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-2

    Success conditionThe contents match what you wrote before the volume was destroyed. That is the round trip proved, not assumed.

  5. 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 -v creates 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-2

    Expected 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-restored

    Success conditioncg-clone exists and holds the same records. cp -a preserves ownership and timestamps, which matters when the application checks them.

  6. 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 above

    Expected resultA logical dump written on the host.

    Success conditionYou know which of the two methods applies. Stopped volume: tar it. Running database: dump it.

  7. 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-clone

    Expected resultBoth names echoed back.

    Success conditiondocker volume ls --filter name=cg- returns nothing but the header.

Troubleshooting

Official sources