Your Docker containers generate data — Postgres databases, Gitea repositories, Vaultwarden vaults, Grafana dashboards, Authentik configurations. If any of this data disappears, how fast can you recover?
If the answer is “uh, I should probably back that up,” this guide is for you.
Restic is an open-source backup tool purpose-built for this scenario. It encrypts every snapshot, deduplicates data across backup runs, supports multiple storage backends (local, S3, SFTP, REST server, Backblaze B2), and integrates cleanly into Docker environments without installing anything on your host.
This guide covers deploying restic as a Docker container, configuring automated daily backups to both a local repository and an S3-compatible offsite target, setting retention policies, and walking through a full disaster recovery exercise.
Why Restic Over Other Backup Tools
| Feature | Restic | Borg | Duplicati | rsync + tar |
|---|---|---|---|---|
| Deduplication | Yes | Yes | Yes | No |
| Encryption | Native AES-256-GCM | Native | Native | External |
| S3/B2/SFTP | Native | SFTP only | Yes | Via piping |
| Docker native | Explicit | Via wrapper | Yes | Manual |
| Restore speed | Fast | Moderate | Slow | Fast |
| Single-file restore | Yes (mount as FUSE) | Yes (FUSE mount) | Web UI only | Full archive |
| Docker image 50MB | Yes | No official | Yes | N/A |
| Learning curve | Low | Medium | Low | Low |
When to choose restic: You want Docker-native backup with
deduplication, encryption, cloud offsite storage, and simple CLI
restore. If you only need local incremental tar archives,
consider docker run with tar. If you need a web UI, look at
Duplicati or Kopia.
Architecture Overview
┌──────────────────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Postgres │ │ Gitea │ │ Nextcloud│ ... │
│ │ volume │ │ volume │ │ volume │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Restic │ Daily cron backup │
│ │ Container │◄── runs docker exec │
│ │ │ on volume containers │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ Local │ │ MinIO/S3 │ │ B2/SFTP/ │ │
│ │ Backup │ │ (offsite) │ │ REST Server │ │
│ └──────────┘ └────────────┘ └──────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
The restic container runs on a cron schedule. Each run:
- Uses
docker execto trigger database dumps inside running containers (Postgres, MySQL) - Mounts every Docker volume as read-only
- Creates a restic snapshot with deduplication
- Prunes old snapshots per retention policy
- Forgets and removes expired snapshots
Step 1 — Directory Structure and Environment
|
|
Create the environment file:
|
|
Generate the repository password:
|
|
Step 2 — Docker Compose Stack
|
|
Mount Additional Volumes
Find all Docker volumes on your host:
|
|
For each volume you want backed up, add it to the compose file with
external: true. The mount path inside the container should map
cleanly: volume myapp_data → /volumes/myapp_data.
Step 3 — Initialization Script
Restic repositories need to be initialized. The init script also creates the offsite S3 repository if configured:
|
|
Run it once:
|
|
Store the resulting repository ID and password in your password manager. Without the password, your backups are unrecoverable.
Step 4 — The Backup Script
This is the core. The script runs docker exec commands inside
database containers before backing up volumes, then creates
deduplicated snapshots:
|
|
Make it executable:
|
|
Step 5 — Cron Schedule
Run the backup daily at 2 AM using the host’s cron. You can also run it from within the container using a lightweight cron image, but host cron is simpler and more reliable:
|
|
Add:
# restic docker backup — daily at 02:00
0 2 * * * cd /opt/restic && docker compose exec -T restic /scripts/backup.sh >> /var/log/restic-cron.log 2>&1
Test the cron runs by executing it directly:
|
|
The first run takes longer because restic has to read every byte and build the initial index. Subsequent runs are fast — typically seconds to a few minutes depending on how much data changed, because restic deduplicates at the block level.
Step 6 — Verification and Monitoring
Snapshot List
|
|
Output shows all snapshots with timestamps, host, and tags:
repository /data/restic-repo opened successfully
ID Time Host Tags Paths
────────────────────────────────────────────────────────────────
a1b2c3d4 2026-05-19 02:00:00 srv1 daily /volumes
e5f6g7h8 2026-05-18 02:00:00 srv1 daily /volumes
i9j0k1l2 2026-05-17 02:00:00 srv1 daily /volumes
Stats Per Snapshot
|
|
Integrate with Healthchecks.io
This is critical for confidence. Setup:
- Create a check at healthchecks.io (or
self-host with the
healthchecks/dockerimage) - Set the ping URL in your
.env:HEALTHCHECKS_URL=https://hc-ping.com/your-uuid - The backup script pings after successful completion
If the backup doesn’t run, Healthchecks sends an alert. This has caught failed backups within 12 hours for me — way better than finding out during a disaster.
Drive Failure Simulation
Test by simulating a full mount failure:
|
|
Step 7 — Disaster Recovery Walkthrough
This is the most important section. Test this now, not when something breaks.
7a — Restore All Volumes to Latest
|
|
Run from inside the container:
|
|
Then copy individual volume data back:
|
|
7b — Restore a Specific File
Restic can mount snapshots as a FUSE filesystem for interactive browsing:
|
|
In another terminal:
|
|
Unmount when done:
|
|
7c — Full Disaster: Rebuild from Scratch
When the entire Docker host crashes:
- Install Docker and Docker Compose on a new machine
- Set up the restic container with the same
.envand password - Initialize won’t overwrite — it errors if the repo exists
- Restore:
|
|
- For each volume, recreate and populate:
|
|
- Deploy the original compose stacks — data is waiting in the restored volumes
7d — Offsite Restore from S3
If the local machine is gone, restore from S3:
|
|
The same commands work — just point at the S3 repository URL.
Step 8 — Advanced Patterns
Multiple Tags and Retention Policies
Use different tags for different backup tiers:
|
|
Forget with tag filters:
|
|
Excluding Non-Essential Data
Some volumes have cache or temp directories that don’t need backup:
|
|
Parallel Backup to Multiple Destinations
The script already handles local + S3. Add more:
|
|
Each repository has its own independent retention policy. The local repo keeps more snapshots for fast recovery; the S3 repo keeps fewer to save on storage cost.
Pre-Backup Hooks for Database Dumps
For databases that don’t have docker exec access mounted in the
compose file, use a sidecar pattern:
|
|
Then have restic back up postgres_backups volume instead of the
raw Postgres data directory. This guarantees a consistent SQL-level
dump instead of a potentially inconsistent binary copy.
Step 9 — Hardening the Setup
Volume Mount Permissions
Restic runs as the root user in the container (by default). This
is necessary to read all Docker volumes. If you want to tighten it:
|
|
Encrypt the Restic Password
The restic password is stored in a file with chmod 600. For extra
security, use a secrets manager:
|
|
Backup the Backup
The restic local repository at /data/backups/restic-repo itself
should be included in your Proxmox backup strategy. It’s just files
on disk — Proxmox Backup Server or ZFS snapshots handle this level.
For the offsite S3, ensure bucket versioning is enabled. If ransomware encrypts your host and the backup script runs, restic will snapshot the encrypted data as a “valid” backup. Versioning lets you roll back to pre-encryption snapshots.
Alert on Failure
Add a failure notification to the backup script:
|
|
This pings Healthchecks with /fail if any command exits
non-zero, immediately alerting you.
Step 10 — Monitoring Dashboard
Add a restic exporter to Prometheus for visualized backup monitoring:
|
|
Add a Prometheus scrape target:
|
|
Then build a Grafana dashboard showing:
- Last successful backup timestamp
- Snapshot count over time
- Repository size growth
- Backup duration
- Integrity check results
The Cost Argument
Let’s be concrete about what this setup costs:
- restic container: 50MB RAM, negligible CPU (runs 2-5 minutes daily)
- Local storage: ~5-15GB for 30 days of snapshots of 20GB of Docker volumes (deduplication works well for databases)
- S3 storage: ~2-10GB/month at $0.023/GB = $0.05-$0.23/month
- Backblaze B2: Same data at ~$0.006/GB = $0.01-$0.06/month
- Healthchecks.io: Free tier
For under $1/month, you get encrypted, deduplicated, offsite backups with 30-day retention and instant alerting if backups fail. Compare that to the cost of losing your Vaultwarden password vault, Gitea repositories, or Grafana dashboards.
Putting It All Together
Step-by-step deployment checklist:
- Create directory structure:
mkdir -p /opt/restic/{scripts,data} - Write
.envwith repository locations and credentials - Generate a 32-byte random password for restic
- Write
docker-compose.ymlmounting all Docker volumes - Write
scripts/init.shand run it to create repositories - Write
scripts/backup.shwith pre-backup, backup, prune, and check steps - Add cron entry to run the backup script daily at 02:00
- Set up Healthchecks.io for failure alerting
- Test a restore by running
restic restore latest - Document the password securely — without it, backups are unrecoverable
Recovery time objectives with this setup:
| Scenario | RTO | RPO |
|---|---|---|
| Accidentally deleted volume data | 10 minutes | Up to 24 hours |
| Docker host drive failure | 1-2 hours | Up to 24 hours |
| Full site disaster (use S3 backup) | 2-4 hours | Up to 24 hours |
| Ransomware (restore from S3 versioned) | 2-4 hours | Variable |
Summary
A homelab without backups is a time bomb. Restic removes every excuse for avoiding them:
- Docker-native — runs as a container, mounts volumes read-only, does not require agent installation inside application containers
- Encrypted by default — AES-256-GCM with a single password. No plaintext data ever hits disk or S3
- Deduplicated — 20 daily snapshots of changing databases take about as much space as the latest full backup plus the daily diffs
- Multi-destination — backup to local disk, S3, B2, SFTP, or any rest-server target in one run
- Scriptable restore — full disaster recovery is a shell script away. No proprietary tools, no vendor lock-in
Set it up once, test the restore, and stop worrying about losing data. The $0.10/month for S3 storage is the cheapest insurance your homelab will ever have.