Docker & DevOps

Back up Docker volumes with restic and restore them

Docker named volumes live under /var/lib/docker/volumes. This guide shows how to snapshot them with restic, verify the snapshot and restore it into a new volume.

IntermediateLinux server6 min read

Backing up Docker volumes with restic gives you versioned, deduplicated snapshots of the data your containers write. Volumes are just directories on the host, so restic can read them directly — no Docker plugin needed. This guide shows you where those directories live, how to snapshot them safely, and how to restore a snapshot into a new volume on a Linux VPS.

Before you start

You need:

  • A Linux VPS running Docker. If Docker is not installed yet, see Install Docker and Docker Compose on a Linux VPS.
  • Root or sudo access. /var/lib/docker is root-only.
  • restic installed on the host.
  • A repository location outside the volumes you are backing up — a second disk, a mounted network share, or a remote repo over SFTP or S3.
  • The name of the volume you want to protect (docker volume ls shows them).

Install restic with your package manager:

Bash
sudo apt update && sudo apt install -y restic

On AlmaLinux or Rocky, restic is usually available from EPEL. Check your distribution's package list if dnf install restic finds nothing, and confirm the install with restic version.

Note: You are responsible for your own backups. Confirm that your repository location is separate from the disk holding /var/lib/docker, otherwise one disk failure takes both copies.

Find the volume mountpoint

Docker stores named volumes under /var/lib/docker/volumes/<name>/_data by default. Inspect the one you care about:

Bash
sudo docker volume ls
sudo docker volume inspect app_data

The output includes the path restic will read:

[
    {
        "CreatedAt": "2026-01-01T10:00:00Z",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/app_data/_data",
        "Name": "app_data",
        "Scope": "local"
    }
]

For scripting, print just the path:

Bash
VOL=app_data
sudo docker volume inspect "$VOL" --format '{{ .Mountpoint }}'

Note: If you moved Docker's data directory, the path differs. Check sudo docker info --format '{{.DockerRootDir }}' and use that as the prefix. Anonymous volumes created by Compose have hash names; map them to a service with sudo docker inspect <container> --format '{{ json.Mounts }}'.

Initialise the restic repository

Create a password file that only root can read, then set the environment variables your shell will reuse:

Bash
sudo sh -c 'umask 077; openssl rand -base64 32 > /root/.restic-pass'

export RESTIC_REPOSITORY=/mnt/backups/restic
export RESTIC_PASSWORD_FILE=/root/.restic-pass

Initialise the repository:

Bash
sudo -E restic init

Warning: The restic password is the only key to the repository. Store a copy in your password manager now. Lose it and the snapshots are unreadable.

Tip: If sudo -E drops your exported variables on your system, pass the values on each command instead: sudo restic -r /mnt/backups/restic --password-file /root/.restic-pass....

Back up Docker volumes with restic

Restoring a container's data only works if the files inside the volume are consistent. For a plain file service, a running container is usually fine. For a database, it is not.

Warning: Copying database files while the database is writing can produce a snapshot that looks fine but fails on restore. Either stop the container, or dump the database first — see Back Up MariaDB with mariabackup, Back Up and Restore MongoDB with mongodump and mongorestore or Back Up and Restore Redis Data.

Stop the stack if you need a clean copy:

Bash
sudo docker compose stop app

Take the snapshot:

Bash
sudo -E restic backup \
  /var/lib/docker/volumes/app_data/_data \
  --tag docker-volume --tag app_data --host vps01

restic prints the files scanned, how many were new, how much was added, and a snapshot ID. Tags let you find this snapshot again later, so always include the volume name.

To cover several volumes in one run:

Bash
for VOL in app_data app_uploads; do
  sudo -E restic backup "/var/lib/docker/volumes/$VOL/_data" \
    --tag docker-volume --tag "$VOL" --host vps01
done

Start the containers again, then set a retention policy so the repository does not grow forever:

Bash
sudo -E restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

--prune rewrites the repository and can be slow; it is fine to run it weekly rather than on every backup. For a schedule, drive these commands from cron or a systemd timer — the pattern is described in Back up your Linux VPS with rsync, restic and cron.

Restore a volume from a snapshot

List the snapshots for the volume:

Bash
sudo -E restic snapshots --tag app_data

Restore the newest one into a temporary directory:

Bash
sudo -E restic restore latest --tag app_data --target /tmp/restore

The files land under the original absolute path inside the target:

/tmp/restore/var/lib/docker/volumes/app_data/_data

Create a new volume and copy the data in:

Bash
sudo docker volume create app_data_restored
sudo cp -a /tmp/restore/var/lib/docker/volumes/app_data/_data/. \
  /var/lib/docker/volumes/app_data_restored/_data/

Check the result with a throwaway container:

Bash
sudo docker run --rm -v app_data_restored:/data alpine:3.20 ls -la /data

Warning: Replacing a live volume deletes the current data. Stop the stack first, and keep the restored copy until you have confirmed the application reads it correctly.

To swap it in, stop the stack, remove the old volume, recreate it under the original name, and copy the restored files into place:

Bash
sudo docker compose down
sudo docker volume rm app_data
sudo docker volume create app_data
sudo cp -a /var/lib/docker/volumes/app_data_restored/_data/. \
  /var/lib/docker/volumes/app_data/_data/
sudo docker compose up -d

Docker does not rename volumes, which is why the name is recreated rather than reused. If docker volume rm reports the volume is in use, a container still holds it — run sudo docker ps -a and remove the leftover container.

Check it works

Verify the repository is readable and the snapshots are intact:

Bash
sudo -E restic check --read-data-subset=5%
sudo -E restic snapshots

Then confirm the application starts against the restored data and check its logs:

Bash
sudo docker compose up -d
sudo docker compose logs --tail=50 app

Finally, restore a snapshot into a scratch volume on a regular basis — verification catches missing files long before you need them.

Troubleshooting

  • Fatal: unable to open repository … wrong password — the environment variables were not passed through sudo. Use sudo -E restic, or supply -r and --password-file explicitly.
  • permission denied while reading /var/lib/docker/volumes/... — you ran restic as an unprivileged user. Prepend sudo; the directory is root-owned.
  • Snapshot restores but the database will not start — the files were copied while the database was running. Stop the container or dump the database before backing up.
  • Repository grows faster than expected — large log or cache files inside the volume are being snapshotted. Add --exclude patterns for those paths, then run restic forget --prune.
  • Application can't write to the restored volume — file ownership changed during the copy. Fix it to match the UID/GID the container runs as, for example sudo chown -R 1000:1000 /var/lib/docker/volumes/app_data/_data.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.