How to Back Up Docker Volumes to MinIO with Restic
Send Docker volume backups to MinIO with Restic for a practical off-host backup routine that still feels manageable on a small self-hosting stack.
How to back up Docker volume data to S3-compatible storage, verify snapshots, prune old data, and practice restore steps.
Self-hosters who want off-host backups without adopting a heavy enterprise backup suite.
Backing up live database files without a safe dump or pause step can give you a backup that restores badly.
Before you begin
- A server running Docker or Docker Compose.
- A MinIO server or another S3-compatible endpoint you control.
- Access key, secret key, bucket name, and endpoint URL for MinIO.
- Enough free space for temporary dumps if you back up databases safely.
Backing up to a local disk is better than nothing, but it still leaves you exposed to host failure, filesystem damage, or operator mistakes on the same machine. Restic plus MinIO is a solid open-source-first combination because it gives you encrypted snapshots, retention, and off-host storage without a giant backup platform.
Step 1: Prepare the MinIO bucket and credentials
Create a bucket in MinIO for backups, such as docker-restic-backups. Then create or choose a MinIO access key with permission to read and write only that bucket if possible.
Write down these values:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYRESTIC_REPOSITORY, for examples3:https://minio.example.com/docker-restic-backupsRESTIC_PASSWORD
Use a strong repository password and store it somewhere protected. If you lose the Restic password, your backups may be unrecoverable.
Step 2: Install Restic and configure the environment
On Ubuntu or Debian:
sudo apt update
sudo apt install -y resticCreate a protected environment file:
sudo mkdir -p /etc/restic
sudo nano /etc/restic/minio.envAdd:
AWS_ACCESS_KEY_ID=replace-me
AWS_SECRET_ACCESS_KEY=replace-me
RESTIC_REPOSITORY=s3:https://minio.example.com/docker-restic-backups
RESTIC_PASSWORD=replace-with-a-long-random-passwordLock down the file:
sudo chmod 600 /etc/restic/minio.envLoad the environment and initialize the repository once:
set -a
source /etc/restic/minio.env
set +a
restic initIf your MinIO endpoint uses a self-signed certificate or nonstandard settings, test connectivity before building the whole backup routine.
Step 3: Create a backup script that respects app data
Do not blindly archive every live database directory. For file-based apps, direct volume backups are often fine. For PostgreSQL or MariaDB, create dumps first, then back up the dumps and any important config.
Create /usr/local/bin/restic-docker-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
set -a
source /etc/restic/minio.env
set +a
WORKDIR=/var/backups/docker
DATE=$(date -u +%Y-%m-%dT%H-%M-%SZ)
HOST=$(hostname)
mkdir -p "$WORKDIR/$DATE"
# Example logical database dump
if docker ps --format '{{.Names}}' | grep -q '^postgres$'; then
docker exec postgres pg_dumpall -U postgres > "$WORKDIR/$DATE/postgres.sql"
fi
# Example file copy from mounted volume paths
rsync -a /var/lib/docker/volumes/myapp_data/_data/ "$WORKDIR/$DATE/myapp_data/"
rsync -a /var/lib/docker/volumes/ghost_content/_data/ "$WORKDIR/$DATE/ghost_content/"
restic backup "$WORKDIR/$DATE" --tag docker-volumes --tag "$HOST"
restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6
rm -rf "$WORKDIR/$DATE"Make it executable:
sudo chmod +x /usr/local/bin/restic-docker-backup.shAdjust the container names and volume paths for your own stack. If your Docker data lives somewhere cleaner through bind mounts, use those paths instead.
Step 4: Run the backup, verify it, and schedule it
Run the script manually first:
sudo /usr/local/bin/restic-docker-backup.shList snapshots:
set -a
source /etc/restic/minio.env
set +a
restic snapshotsCheck repository health:
restic checkSchedule the job with cron or a systemd timer. A simple cron example:
sudo crontab -e30 2 * * * /usr/local/bin/restic-docker-backup.sh >> /var/log/restic-docker-backup.log 2>&1After the first successful run, practice a restore into a temporary directory:
mkdir -p /tmp/restic-restore-test
restic restore latest --target /tmp/restic-restore-testInspect the restored files and confirm the expected dump files or volume contents are present. A backup you never test is still a gamble.
Expected outcomes
- Your backup data is stored off-host in MinIO.
- Snapshots appear in Restic and can be listed.
- Retention removes older data on purpose instead of filling the bucket forever.
- You have tested that files can restore into a separate path.
Rollback and recovery notes
If the script is wrong, stop the schedule before it keeps producing bad backups. Fix the source paths or dump commands, then run the script manually again.
If you need to restore a volume, stop the affected container first so the app does not write over recovered data:
docker compose stop app
restic restore latest --target /tmp/restore
rsync -a /tmp/restore/var/backups/docker/2026-05-07T02-30-00Z/myapp_data/ /var/lib/docker/volumes/myapp_data/_data/
docker compose start appFor databases, restore from logical dumps using the database's supported import path instead of copying raw files back into a running container.
Troubleshooting common Restic and MinIO issues
Restic cannot connect to the repository.
Check the MinIO endpoint URL, credentials, firewall rules, and TLS settings.
Backups run, but snapshots are missing expected app data.
You may be backing up the wrong volume path or forgot that the app writes to a bind mount instead.
The restore contains files, but the app still fails.
The app may need a database import, ownership fix, or matching config files.
The bucket grows too fast.
Review retention settings, temporary dump size, and whether you are backing up redundant paths.
What to do next
Once backups are leaving the host safely, the next upgrade is wiring failure notifications into the backup routine. Continue with How to Send Self-Hosting Alerts with ntfy.
