Deploy Automated Docker Backups with Restic: Encryption, Dedup, and One-Command Restore
Already downloaded the Homelab Backup Automation Stack kit? All the files below are pre-configured in your download. This tutorial walks through building from scratch so you understand what each piece does and can customize it. Follow along to learn the stack, or use it as a reference when you need to modify something.
This tutorial walks through deploying the Homelab Backup Automation Stack from scratch. By the end you'll have encrypted, deduplicated backups for all your Docker services -- configured through labels, verified automatically, and scored for health.
What You'll Set Up
- restic 0.18.1 as the backup engine (AES-256 encryption, content-defined chunking)
- Backrest v1.11.2 for a web UI to browse snapshots
- shoutrrr v0.8.0 for notifications to Discord, Slack, ntfy, or email
- 11 scripts handling backup, restore, verification, health, metrics, and DR docs
- 5 profiles for different service types (database, critical, config-only, large-media, default)
Total RAM: ~768MB during backups (512MB restic + 256MB Backrest), ~20MB idle. Shoutrrr runs on-demand via docker run --rm.
Prerequisites
- Docker 24+ with Docker Compose v2
- Linux host (Ubuntu 22.04+, Debian 12+, Proxmox 8)
- 5GB+ free disk space
- 15 minutes
Check your versions:
docker --version # Need 24+
docker compose version # Need v2+
Step 1: Download and Extract
cd /opt # or wherever you keep service stacks
unzip homelab-backup-stack-v1.0.zip
cd product-kit
The directory structure:
product-kit/
├── docker-compose.yml
├── .env.example
├── setup.sh
├── configs/
├── scripts/
└── docs/
Step 2: Run the Setup Wizard
chmod +x setup.sh
./setup.sh
The wizard walks through:
- Encryption password -- press Enter to auto-generate a strong one. Write it down. You cannot recover backups without it.
- Storage backend -- local (default), Backblaze B2, AWS S3, or SFTP.
- Backup schedule -- daily at 2 AM works for most homelabs.
- Retention -- how many daily, weekly, and monthly snapshots to keep.
- Notifications -- optional. Discord, Slack, ntfy, Gotify, email, or Telegram.
- Backrest port -- 9898 by default.
The wizard writes .env, initializes the restic repository, and starts both containers.
If you prefer to configure manually: cp .env.example .env, edit the values, then docker compose up -d.
Step 3: Verify the Stack Is Running
docker compose ps
You should see two healthy containers:
NAME IMAGE STATUS
backup-restic restic/restic:0.18.1 Up (healthy)
backup-backrest garethgeorge/backrest:v1.11.2 Up (healthy)
Notifications use shoutrrr via docker run --rm on demand -- no idle sidecar needed.
Open Backrest at http://your-server:9898 to confirm the UI loads.
Step 4: Add Backup Labels to Your Services
This is where the stack becomes useful. You tell it what to back up by adding Docker labels to your existing services.
Example: PostgreSQL
services:
postgres:
image: postgres:17
# ... your existing config ...
labels:
backup.nxsi.enable: "true"
backup.nxsi.profile: "database"
backup.nxsi.priority: "10"
backup.nxsi.verify-type: "database"
backup.nxsi.verify-image: "postgres:17"
The database profile auto-detects that this is PostgreSQL (from the image name) and generates a pg_dump command as a pre-hook. You don't need to write the dump command yourself. Detection works for postgres, pgvector, TimescaleDB, and PostGIS images -- any postgres-compatible container gets the right hook automatically.
Example: Vaultwarden
services:
vaultwarden:
image: vaultwarden/server:latest
# ... your existing config ...
labels:
backup.nxsi.enable: "true"
backup.nxsi.profile: "critical"
backup.nxsi.verify-url: "http://localhost:80"
The critical profile stops the container during backup for data consistency and keeps snapshots for 30 days / 12 weeks / 12 months.
Example: Nginx (or any standard service)
services:
nginx:
image: nginx:alpine
labels:
backup.nxsi.enable: "true"
No profile needed. The default profile backs up all named volumes with 7d/4w/3m retention.
Using the Label Configurator
Not sure which profile to use? The interactive configurator auto-detects and auto-applies:
./scripts/add-service.sh # List all containers + suggested profiles
./scripts/add-service.sh nxsi-postgres # Configure and label a specific service
It lists all running containers, shows which ones have labels, and suggests the right profile based on the image name. Select a container and the script auto-detects your compose file, injects the labels, and offers to recreate the container -- no manual copy-paste needed.
Step 5: Run Your First Backup
./scripts/backup.sh
The output walks through each service:
=== Backup Orchestrator ===
Started: 2026-02-18 02:00 AM CST
[OK] Found 3 service(s) to back up
--- [1/3] homelab-postgres ---
[OK] Auto-detected database hook for homelab-postgres
[OK] Profile: database | Stop: false | Volumes: pgdata
[OK] Running pre-hook: pg_dump -Fc -U postgres mydb -f /tmp/backup/dump.sql
[OK] Database dump staged
[OK] Snapshot: a3f2c891 | Added: 12.4 MiB (4.2 MiB stored) | Time: 8s
--- [2/3] homelab-vaultwarden ---
[OK] Profile: critical | Stop: true | Volumes: vw-data
[OK] Stopping homelab-vaultwarden for consistent backup
[OK] Restarted homelab-vaultwarden
[OK] Snapshot: b7e1d456 | Added: 2.1 MiB (1.8 MiB stored) | Time: 4s
--- [3/3] homelab-nginx ---
[OK] Profile: default | Stop: false | Volumes: auto
[OK] Snapshot: c9a8f012 | Added: 856 KiB (412 KiB stored) | Time: 2s
=== Backup Summary ===
Duration: 14s
A few things to notice: the database pre-hook ran automatically (the dump gets staged into the restic container for backup), Vaultwarden was stopped and restarted, and the parenthetical after each "Added" size shows how much was actually stored after deduplication. Subsequent runs store much less -- the dedup ratio for database dumps typically runs 5-10x.
To preview without executing anything:
./scripts/backup.sh --dry-run
Step 6: Verify Your Backups Can Restore
This is the capability no other homelab tool has.
./scripts/verify.sh --latest
For each service, the script:
- Restores the latest snapshot to a temp directory
- For databases: spins up a temp DB container, imports the dump, runs queries
- For apps: spins up a temp container, checks the health endpoint
- For files: counts restored files, checks for corruption
- Reports PASS or FAIL
- Cleans up all temp containers automatically
=== Backup Restore Verification ===
--- Verifying: homelab-postgres (snapshot a3f2c891, profile database) ---
[OK] Restoring snapshot to staging...
[OK] Database verification...
[OK] Starting temp container: verify-db-1708234567 (postgres:17)
[OK] Waiting for database to be ready...
[OK] Dump imported successfully
[OK] SELECT 1: passed
[OK] Tables found: 42
[OK] Largest table (public.events): 15234 rows
--- Verifying: homelab-vaultwarden (snapshot b7e1d456, profile critical) ---
[OK] Application verification...
[OK] Container started, health check OK
--- Verifying: homelab-nginx (snapshot c9a8f012, profile default) ---
[OK] File verification...
[OK] Files restored: 23
[OK] Restic repo integrity check passed (10% sample)
=== Verification Results ===
SERVICE SNAPSHOT TYPE RESULT DETAILS
homelab-postgres a3f2c891 database PASS 42 tables, import OK
homelab-vaultwarden b7e1d456 app PASS Container started, health check OK
homelab-nginx c9a8f012 files PASS 23 files, 0 empty
Passed: 3 | Failed: 0 | Warnings: 0
If verification fails, you know before you need the backup -- not during a 3 AM disaster.
Step 7: Check Backup Health
./scripts/backup-health.sh
Each service gets a 0-100 score based on recency, verification status, schedule consistency, and storage integrity.
=== Backup Health Report ===
SERVICE SCORE RECENCY VERIFY CONSISTENCY STORAGE RECOMMENDATION
homelab-postgres 100 current pass solid healthy
homelab-vaultwarden 100 current pass solid healthy
homelab-nginx 100 current pass solid healthy
Overall: 100/100 across 3 service(s)
[OK] Backups are healthy
Anything below 80 gets a recommendation. A service that hasn't been verified shows "Run verify.sh --latest." One that missed a scheduled backup shows "Check cron schedule."
Step 8: Set Up Automated Scheduling
Install the cron schedule:
crontab configs/cron/backup-schedules.cron
This configures:
- Daily backup at 2:00 AM
- Weekly verification on Sunday at 4:00 AM
- Prometheus metrics export every 5 minutes
- DR runbook regeneration on Monday mornings
- Health report on Sunday mornings
Adjust the INSTALL_PATH in the cron file to match your installation directory.
Step 9: Configure Storage (Optional)
By default, backups go to ./backups/ on the local disk. For offsite protection, add a cloud backend.
Backblaze B2 (Recommended for Most Homelabs)
Edit .env:
RESTIC_REPOSITORY=b2:your-bucket-name
B2_ACCOUNT_ID=your-b2-key-id
B2_ACCOUNT_KEY=your-b2-application-key
This uses restic's native B2 backend, which is faster and simpler than the S3-compatible mode (no endpoint URL or region needed).
Restart restic and initialize the new repo:
docker compose restart restic
docker exec backup-restic restic init
Cost: ~$0.005/GB/month. 100GB of backups costs about $0.50/month.
Dual Storage (3-2-1 Rule)
Back up locally AND to B2:
RESTIC_REPOSITORY=/backups/repo
RESTIC_SECONDARY_REPOSITORY=b2:your-bucket
SECONDARY_B2_ACCOUNT_ID=your-b2-key
SECONDARY_B2_ACCOUNT_KEY=your-b2-secret
After each backup, backup.sh automatically copies new snapshots to the secondary repository.
See docs/STORAGE-BACKENDS.md for S3, SFTP, and NFS setup.
Step 10: Generate DR Runbooks
./scripts/dr-generate.sh
This creates a docs/recovery/SERVICE.md file for each service with step-by-step recovery instructions: pull image, restore backup, create volumes, import data, start service, verify. Environment variable names are included but values are redacted.
If your server dies, these runbooks tell you exactly how to rebuild each service from backup.
What to Customize
Profiles: Create custom profiles in configs/profiles/ for services that don't fit the defaults. See docs/CUSTOMIZATION.md.
Monitoring integration: The backup-metrics.sh script exports Prometheus textfile metrics. Add it to cron and point Node Exporter's textfile collector at the output file. See the Grafana Backup Dashboard guide for the full setup with pre-built panels and alert rules.
Multi-host: Multiple servers can share the same remote repository. Restic tags snapshots with host:$(hostname) automatically.
Migration: Running Duplicati, docker-volume-backup, or manual cron backups? Run ./scripts/migrate.sh for step-by-step migration guidance.
The Homelab Backup Automation Stack includes everything in this tutorial: Docker Compose, setup wizard, 11 scripts, 5 profiles, and 4 documentation guides. Available at nxsi.io.