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
sudoaccess./var/lib/dockeris root-only. resticinstalled 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 lsshows them).
Install restic with your package manager:
sudo apt update && sudo apt install -y resticOn 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:
sudo docker volume ls
sudo docker volume inspect app_dataThe 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:
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:
sudo sh -c 'umask 077; openssl rand -base64 32 > /root/.restic-pass'
export RESTIC_REPOSITORY=/mnt/backups/restic
export RESTIC_PASSWORD_FILE=/root/.restic-passInitialise the repository:
sudo -E restic initWarning: 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:
sudo docker compose stop appTake the snapshot:
sudo -E restic backup \
/var/lib/docker/volumes/app_data/_data \
--tag docker-volume --tag app_data --host vps01restic 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:
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
doneStart the containers again, then set a retention policy so the repository does not grow forever:
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:
sudo -E restic snapshots --tag app_dataRestore the newest one into a temporary directory:
sudo -E restic restore latest --tag app_data --target /tmp/restoreThe files land under the original absolute path inside the target:
/tmp/restore/var/lib/docker/volumes/app_data/_dataCreate a new volume and copy the data in:
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:
sudo docker run --rm -v app_data_restored:/data alpine:3.20 ls -la /dataWarning: 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:
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 -dDocker 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:
sudo -E restic check --read-data-subset=5%
sudo -E restic snapshotsThen confirm the application starts against the restored data and check its logs:
sudo docker compose up -d
sudo docker compose logs --tail=50 appFinally, 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 throughsudo. Usesudo -E restic, or supply-rand--password-fileexplicitly.permission deniedwhile reading/var/lib/docker/volumes/...— you ran restic as an unprivileged user. Prependsudo; 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
--excludepatterns for those paths, then runrestic 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
- Docker volumes vs bind mounts: store container data safely to pick the right storage type before you back it up.
- Back up your Linux VPS with rsync, restic and cron to run these snapshots on a schedule.
- Self-host Nextcloud with Docker Compose, MariaDB and Redis for a realistic stack whose volumes are worth protecting.
