nXsi
HomeProductsBlogGuidesMCP ServersServicesAbout
HomeProductsBlogGuidesMCP ServersServicesAbout
nXsi

Practical guides, automation tools, and self-hosted products for developers and homelabbers.

Content

  • Blog
  • Products
  • Guides
  • MCP Servers

Resources

  • About
  • Services
  • Support
  • Privacy Policy

Newsletter

Weekly AI architecture insights. No spam.

© 2026 nXsi Intelligence. All rights reserved.
  1. Home
  2. Blog
  3. Deploy Automated Docker Backups with Res…
TutorialintermediateFebruary 21, 2026·7 min read·21 min read hands-on

DeployAutomatedDockerBackupswithRestic:Encryption,Dedup,andOne-CommandRestore

Step-by-step tutorial for deploying a Docker-label-driven backup stack with restic encryption, automated restore verification, and health scoring. Two services, 11 scripts, 5 profiles, deployed in 15 minutes.

homelabbackupresticdocker-composetutorial
Share
XLinkedIn
Table of Contents

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:

  1. Encryption password -- press Enter to auto-generate a strong one. Write it down. You cannot recover backups without it.
  2. Storage backend -- local (default), Backblaze B2, AWS S3, or SFTP.
  3. Backup schedule -- daily at 2 AM works for most homelabs.
  4. Retention -- how many daily, weekly, and monthly snapshots to keep.
  5. Notifications -- optional. Discord, Slack, ntfy, Gotify, email, or Telegram.
  6. 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:

  1. Restores the latest snapshot to a temp directory
  2. For databases: spins up a temp DB container, imports the dump, runs queries
  3. For apps: spins up a temp container, checks the health endpoint
  4. For files: counts restored files, checks for corruption
  5. Reports PASS or FAIL
  6. 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.

On this page

Get weekly AI architecture insights

Patterns, lessons, and tools from building a production multi-agent system. Delivered weekly.

Series: Homelab Backup Automation StackPart 2 of 4
← Previous

I Built a Docker-Aware Backup Stack with Automated Restore Verification — Here's the Full Build Log

Next →

Every Backup Tool Promises Reliability. Here's How to Actually Verify Yours Can Restore.

Series: Homelab Backup Automation StackPart 2 of 4
← Previous

I Built a Docker-Aware Backup Stack with Automated Restore Verification — Here's the Full Build Log

Next →

Every Backup Tool Promises Reliability. Here's How to Actually Verify Yours Can Restore.

Related Product

Homelab Backup Automation Stack — Docker Labels + Restic + Restore Verification

A Docker Compose backup stack with restic encryption, 5 profiles (database, critical, config-only, large-media, default), 11 automation scripts, automated restore verification, health scoring, and DR runbook generation. Add Docker labels, run the setup wizard, done.

Get Free Download

Read Next

Build Log9 min

I Built a Docker-Aware Backup Stack with Automated Restore Verification — Here's the Full Build Log

A chronological build log of creating a Docker-label-driven backup orchestration system with restic encryption, deduplication, 5 profiles, and the homelab's first automated restore verification.

Guide6 min

Every Backup Tool Promises Reliability. Here's How to Actually Verify Yours Can Restore.

Why automated restore verification is the missing piece in homelab backups, and how to implement it with temporary containers and health checks. Enterprise-grade confidence at zero cost.

Guide6 min

The 5 Docker Backup Profiles Every Homelab Needs (and the Label System That Configures Them)

A pattern-based guide to Docker backup strategies: 5 profiles for databases, critical services, configs, media, and general volumes -- all configured through container labels with per-service overrides.