How to Set Up Restic Backups for Docker Volumes
Start with a simple Restic repository on a local disk or mounted backup path, make your Docker data backup-aware, and prove you can restore before you add off-host complexity.
How to protect Docker volume data with Restic using a direct filesystem repository, safe dump habits, retention, and restore drills.
Operators who need a smaller first backup system before they move to S3, MinIO, or another off-host target.
Copying live database files or never practicing restore is how a backup routine becomes a false sense of safety.
Before you begin
- Know which Docker volumes or bind mounts actually hold important data.
- Have a second disk, mounted backup path, or another filesystem location that is not your app's live data path.
- Know which services need an app-aware dump instead of a raw file copy.
- Be ready to test a restore into a separate directory before you automate anything.
This guide is deliberately narrower than an off-host backup design. The goal here is to build a clean local-first baseline that is easy to understand, easy to verify, and good enough to prove your backup and restore habits. Once that routine is solid, moving the same workflow to MinIO, S3, or another remote target is much safer.
Step 1: Start with a direct filesystem repository on purpose
Restic works well against object storage, but that adds another layer of credentials, endpoints, TLS behavior, and network failure cases. For many small self-hosted stacks, the first useful improvement is simpler: write encrypted snapshots to a mounted backup disk, NAS mount, or another protected filesystem path first.
That keeps the backup design focused on the hard parts that matter most on day one:
- Are you backing up the right data?
- Are databases being dumped safely?
- Are snapshots being created and retained?
- Can you actually restore the files you care about?
Step 2: Prepare the repository path and protect the password
Install Restic on the host and choose a repository path such as /srv/backups/restic/docker-volumes or a mounted path like
/mnt/backup-disk/restic/docker-volumes.
sudo apt update
sudo apt install -y restic
sudo mkdir -p /srv/backups/restic/docker-volumes
sudo mkdir -p /etc/restic
sudo chmod 700 /srv/backups/restic/docker-volumes /etc/restic
Create a protected environment file:
sudo nano /etc/restic/docker-volumes.env
Add:
RESTIC_REPOSITORY=/srv/backups/restic/docker-volumes
RESTIC_PASSWORD=replace-with-a-long-random-password
Then lock it down and initialize the repository once:
sudo chmod 600 /etc/restic/docker-volumes.env
set -a
source /etc/restic/docker-volumes.env
set +a
restic init
Keeping the password in a protected file is not glamorous, but it is much better than leaving it in shell history or pasting it into ad hoc commands.
Step 3: Build a backup script that respects the app
Restic backs up files. It does not make database consistency decisions for you. For file-based apps, direct copies from a mounted volume path may be fine. For PostgreSQL, MariaDB, and similar services, create a logical dump first and back up that dump plus any important config.
Create /usr/local/bin/restic-docker-volumes-local.sh:
#!/usr/bin/env bash
set -euo pipefail
set -a
source /etc/restic/docker-volumes.env
set +a
STAMP=$(date -u +%Y-%m-%dT%H-%M-%SZ)
WORKDIR=/var/backups/docker-volumes/$STAMP
HOST=$(hostname)
mkdir -p "$WORKDIR"
# Example logical database dump
if docker ps --format '{{.Names}}' | grep -q '^postgres$'; then
docker exec -T postgres pg_dumpall -U postgres > "$WORKDIR/postgres.sql"
fi
# Example file-based app copies
rsync -a /var/lib/docker/volumes/ghost_content/_data/ "$WORKDIR/ghost_content/"
rsync -a /var/lib/docker/volumes/myapp_uploads/_data/ "$WORKDIR/myapp_uploads/"
restic backup "$WORKDIR" --tag docker-volumes --tag "$HOST"
restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6
rm -rf "$WORKDIR"
Make it executable:
sudo chmod +x /usr/local/bin/restic-docker-volumes-local.sh
Adjust the container names and source paths for your own stack. If your app uses bind mounts instead of Docker-managed volumes, back up the bind mount paths instead.
Step 4: Schedule it, verify it, and keep the repository healthy
Run the script manually first:
sudo /usr/local/bin/restic-docker-volumes-local.sh
Then confirm snapshots exist and the repository is healthy:
set -a
source /etc/restic/docker-volumes.env
set +a
restic snapshots
restic check
If that looks right, schedule the script with cron or a systemd timer. A simple cron example:
sudo crontab -e
30 2 * * * /usr/local/bin/restic-docker-volumes-local.sh >> /var/log/restic-docker-volumes.log 2>&1
Retention matters because a local repository can fill a disk quietly if you only create snapshots and never prune them. The forget --prune step turns retention into an explicit operational rule.
Step 5: Practice restore before calling the system done
The restore drill is the line between a backup routine and a real recovery path.
mkdir -p /tmp/restic-restore-test
set -a
source /etc/restic/docker-volumes.env
set +a
restic restore latest --target /tmp/restic-restore-test
Check that the expected files are present and readable. For database dumps, make sure the dump file is complete and can support the import path you expect.
If you need to restore a file-based volume, stop the app first so it does not overwrite recovered data:
docker compose stop app
rsync -a /tmp/restic-restore-test/var/backups/docker-volumes/2026-06-30T02-30-00Z/myapp_uploads/ /var/lib/docker/volumes/myapp_uploads/_data/
docker compose start app
For databases, restore from the logical dump using the database's own supported import path. Do not copy raw database files back into a live container and hope for the best.
Common mistakes to avoid
Backing up the wrong path:
Confirm whether the app writes to a Docker volume, a bind mount, or somewhere else entirely.
Copying live database files:
Prefer logical dumps or another app-aware backup method when consistency matters.
Leaving the repository password exposed:
Store it in a protected file instead of commands and history.
Never checking snapshots:
Run restic snapshots and restic check on purpose, not just when something feels wrong.
Calling local-first "finished":
A same-host backup path is a start. Mature the workflow later by moving it off-host after the restore routine is proven.