#!/bin/bash # docker-volume-backup.sh # Backs up all named Docker volumes to compressed tar archives. # Supports exclusions, rotation, and optional S3 upload via rclone. # # Usage: sudo bash docker-volume-backup.sh # Restore: sudo bash docker-volume-backup.sh restore set -euo pipefail BACKUP_DIR="${BACKUP_DIR:-/var/backups/docker-volumes}" RETENTION_DAYS="${RETENTION_DAYS:-7}" RCLONE_DEST="${RCLONE_DEST:-}" # e.g. "aws-s3:my-bucket/docker-volumes" EXCLUDE_VOLUMES="${EXCLUDE_VOLUMES:-}" # space-separated list of volumes to skip DATE=$(date '+%Y-%m-%d_%H-%M-%S') BACKUP_PATH="${BACKUP_DIR}/${DATE}" # ── Restore mode ───────────────────────────────────────────────── if [[ "${1:-}" == "restore" ]]; then volume="${2:-}" archive="${3:-}" if [[ -z "$volume" || -z "$archive" ]]; then echo "Usage: $0 restore " exit 1 fi echo "Restoring $volume from $archive..." docker volume create "$volume" 2>/dev/null || true docker run --rm \ -v "${volume}:/volume" \ -v "$(dirname "$archive"):/backup" \ alpine tar xzf "/backup/$(basename "$archive")" -C /volume --strip-components=1 echo "✓ Restored $volume" exit 0 fi # ── Backup mode ────────────────────────────────────────────────── mkdir -p "$BACKUP_PATH" echo "[$(date)] Starting Docker volume backup to $BACKUP_PATH" # Get list of all named volumes mapfile -t volumes < <(docker volume ls --format '{{.Name}}') success=0 failed=0 for volume in "${volumes[@]}"; do # Skip excluded volumes if echo "$EXCLUDE_VOLUMES" | grep -qw "$volume"; then echo " Skipping (excluded): $volume" continue fi archive="${BACKUP_PATH}/${volume}.tar.gz" echo -n " Backing up: $volume ... " if docker run --rm \ -v "${volume}:/volume:ro" \ -v "${BACKUP_PATH}:/backup" \ alpine tar czf "/backup/${volume}.tar.gz" -C /volume . 2>/dev/null; then size=$(du -sh "$archive" | cut -f1) echo "✓ (${size})" ((success++)) else echo "FAILED" ((failed++)) fi done echo "" echo "[$(date)] Backup complete: ${success} succeeded, ${failed} failed" echo "Location: $BACKUP_PATH" echo "Total size: $(du -sh "$BACKUP_PATH" | cut -f1)" # ── Upload to S3 via rclone ────────────────────────────────────── if [[ -n "$RCLONE_DEST" ]]; then if command -v rclone &>/dev/null; then echo "" echo "Syncing to ${RCLONE_DEST}..." rclone sync "$BACKUP_DIR" "$RCLONE_DEST" --progress echo "✓ Sync complete" else echo "WARNING: RCLONE_DEST set but rclone not installed" fi fi # ── Rotate old backups ─────────────────────────────────────────── echo "" echo "Rotating backups older than ${RETENTION_DAYS} days..." find "$BACKUP_DIR" -maxdepth 1 -type d -mtime "+${RETENTION_DAYS}" -exec rm -rf {} + 2>/dev/null || true echo "✓ Rotation complete" echo "" echo "Current backups:" ls -lh "$BACKUP_DIR" | tail -10