01What Needs to Be Backed Up
Docker itself is easy to reinstall, but your data isn't. Here's what you need to preserve:
**Critical:**
• Docker volumes (persistent data)
• Bind-mounted directories
• Docker Compose files
• Environment files (.env)
• Configuration files
**Nice to have:**
• Container logs (if needed for compliance)
• Custom Docker images (if not in registry)
**Not needed:**
• Docker itself (reinstall from packages)
• Downloaded images (re-pull from registries)
Store compose files in Git. That leaves volumes and configs as the main backup targets.
02Backup Strategy Overview
A solid backup strategy follows the 3-2-1 rule:
**3** copies of your data
**2** different storage types
**1** offsite location
**For Docker stacks:**
• Local backups to another disk
• Offsite backup to cloud storage
• Automated with verification
1# Backup directory structure2backups/3├── daily/4│ ├── 2024-01-15/5│ │ ├── volumes/6│ │ ├── configs/7│ │ └── manifest.txt8│ └── 2024-01-14/9├── weekly/10└── offsite/11 └── synced-to-cloud/1213# What goes where:14# - daily/: Last 7 days of backups15# - weekly/: Keep 4 weekly backups16# - offsite/: Sync to cloud storage daily03Backing Up Docker Volumes
Docker volumes are the primary backup target. Here are several methods to back them up.
1# Method 1: Direct tar backup (container stopped)2docker compose stop myservice3docker run --rm -v myvolume:/data -v $(pwd)/backups:/backup alpine \4 tar czf /backup/myvolume-$(date +%Y%m%d).tar.gz -C /data .5docker compose start myservice67# Method 2: Using docker-volume-backup container8services:9 backup:10 image: offen/docker-volume-backup:latest11 environment:12 - BACKUP_CRON_EXPRESSION=0 2 * * *13 - BACKUP_RETENTION_DAYS=714 volumes:15 - myvolume:/backup/myvolume:ro16 - ./backups:/archive17 - /var/run/docker.sock:/var/run/docker.sock:ro1819# Method 3: Rsync from bind mounts20rsync -av --delete /docker/appdata/ /backups/appdata/2122# Method 4: For databases, use native dump tools23docker exec postgres pg_dump -U user dbname > backup.sql24docker exec mariadb mysqldump -u root -p dbname > backup.sqlFor databases, always prefer native dump tools over volume backups. They ensure consistency and are easier to restore.
04Automated Backup Script
A comprehensive backup script that handles multiple services.
1#!/bin/bash2# docker-backup.sh34set -e56BACKUP_ROOT="/backups"7DATE=$(date +%Y%m%d-%H%M)8BACKUP_DIR="$BACKUP_ROOT/daily/$DATE"9COMPOSE_DIR="/docker"1011mkdir -p "$BACKUP_DIR"/{volumes,databases,configs}1213echo "Starting backup at $(date)"1415# Backup docker-compose files and configs16cp -r "$COMPOSE_DIR"/*.yml "$BACKUP_DIR/configs/"17cp -r "$COMPOSE_DIR"/*.env "$BACKUP_DIR/configs/" 2>/dev/null || true18cp -r "$COMPOSE_DIR"/config "$BACKUP_DIR/configs/" 2>/dev/null || true1920# Database backups (while running)21echo "Backing up databases..."22docker exec postgres pg_dump -U postgres mydb | gzip > "$BACKUP_DIR/databases/postgres.sql.gz"23docker exec mariadb mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases | gzip > "$BACKUP_DIR/databases/mariadb.sql.gz"2425# Volume backups26echo "Backing up volumes..."27for volume in $(docker volume ls -q); do28 echo " Backing up $volume..."29 docker run --rm -v "$volume":/data -v "$BACKUP_DIR/volumes":/backup alpine \30 tar czf "/backup/$volume.tar.gz" -C /data .31done3233# Create manifest34echo "Creating manifest..."35ls -la "$BACKUP_DIR"/* > "$BACKUP_DIR/manifest.txt"36docker ps -a >> "$BACKUP_DIR/manifest.txt"3738# Cleanup old backups (keep 7 days)39find "$BACKUP_ROOT/daily" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;4041echo "Backup complete at $(date)"42echo "Backup size: $(du -sh "$BACKUP_DIR" | cut -f1)"05Offsite Backups
Local backups protect against hardware failure but not disasters. Add offsite backup for full protection.
1# Option 1: Rclone to cloud storage2# Configure rclone first: rclone config34# Sync backups to cloud5rclone sync /backups remote:docker-backups --progress67# In cron (after local backup completes)80 4 * * * rclone sync /backups/daily backblaze:mybucket/docker/daily910# Option 2: Restic to various backends11# Initialize repository12restic -r b2:mybucket:docker init1314# Backup with deduplication15restic -r b2:mybucket:docker backup /backups/daily/latest1617# Option 3: Duplicati (GUI-based)18services:19 duplicati:20 image: linuxserver/duplicati:latest21 volumes:22 - /backups:/source:ro23 - ./duplicati-config:/config24 ports:25 - "8200:8200"2627# Option 4: Simple rsync to another server28rsync -avz --delete /backups/ user@backup-server:/docker-backups/Encrypt offsite backups! Use Rclone's crypt backend or Restic's built-in encryption.
06Restore Process
Document and test your restore process before you need it!
1#!/bin/bash2# docker-restore.sh34BACKUP_DIR=$15COMPOSE_DIR="/docker"67if [ -z "$BACKUP_DIR" ]; then8 echo "Usage: $0 /path/to/backup/2024-01-15"9 exit 110fi1112echo "Restoring from $BACKUP_DIR"13echo "This will overwrite current data. Continue? (yes/no)"14read confirm15[ "$confirm" != "yes" ] && exit 11617# Stop all containers18cd "$COMPOSE_DIR"19docker compose down2021# Restore configs22cp "$BACKUP_DIR/configs/"*.yml "$COMPOSE_DIR/"23cp "$BACKUP_DIR/configs/"*.env "$COMPOSE_DIR/" 2>/dev/null || true2425# Restore volumes26for archive in "$BACKUP_DIR/volumes/"*.tar.gz; do27 volume=$(basename "$archive" .tar.gz)28 echo "Restoring volume $volume..."29 docker volume create "$volume" 2>/dev/null || true30 docker run --rm -v "$volume":/data -v "$BACKUP_DIR/volumes":/backup alpine \31 tar xzf "/backup/$volume.tar.gz" -C /data32done3334# Start containers35docker compose up -d3637echo "Restore complete. Verify services are working!"Test restores regularly! A backup that can't be restored is worthless.
07Testing Your Backups
Schedule regular restore tests to verify your backups actually work.
1# Quarterly restore test procedure23# 1. Spin up test environment (VM, spare machine, or cloud instance)4# 2. Install Docker5# 3. Copy latest backup to test environment6# 4. Run restore script7# 5. Verify all services start8# 6. Verify data integrity:9# - Can you log into services?10# - Is recent data present?11# - Do databases respond correctly?12# 7. Document results and any issues1314# Automated integrity check (run monthly)15#!/bin/bash16for archive in /backups/daily/latest/volumes/*.tar.gz; do17 echo "Checking $archive..."18 tar tzf "$archive" > /dev/null || echo "CORRUPT: $archive"19done2021# Database backup verification22gunzip -t /backups/daily/latest/databases/*.sql.gz || echo "CORRUPT backup found"Put restore testing on your calendar. Quarterly tests catch problems before you need the backups.