๐Ÿ’พ

Docker Volumes Backup

๐Ÿ‘จโ€๐Ÿณ Chefโฑ๏ธ 25 minutes

๐Ÿ“‹ Suggested prerequisites

  • โ€ขDocker with volumes

What you'll build

Scripts to backup and restore Docker volumes. You'll protect your PostgreSQL, Redis, and any other service data.

When finished you'll have:

  • Backup script that creates .tar.gz files
  • Restore script to recover data
  • Automatic backup with cron

Step 1: Create a test volume

# Create volume
docker volume create test_data

# Create container with data
docker run --rm -v test_data:/data alpine sh -c "
  echo 'Important data' > /data/file.txt
  echo 'More data' > /data/config.json
  mkdir /data/subdir
  echo 'Nested' > /data/subdir/nested.txt
"

# Verify contents
docker run --rm -v test_data:/data alpine ls -la /data

Step 2: Manual backup

# Backup to .tar.gz file
docker run --rm \
  -v test_data:/source:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/test_data_backup.tar.gz -C /source .

# Verify
ls -lh test_data_backup.tar.gz
tar tzf test_data_backup.tar.gz

Step 3: Backup script

Create backup-volume.sh:

#!/bin/bash
# backup-volume.sh - Docker volume backup

VOLUME_NAME=${1:?Usage: ./backup-volume.sh VOLUME_NAME}
BACKUP_DIR=${2:-./backups}
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${VOLUME_NAME}_${TIMESTAMP}.tar.gz"

mkdir -p "$BACKUP_DIR"

echo "Backing up '$VOLUME_NAME' to '$BACKUP_FILE'..."

docker run --rm \
  -v "$VOLUME_NAME":/source:ro \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf "/backup/${VOLUME_NAME}_${TIMESTAMP}.tar.gz" -C /source .

if [ $? -eq 0 ]; then
  echo "โœ… Backup completed: $BACKUP_FILE"
  echo "   Size: $(ls -lh "$BACKUP_FILE" | awk '{print $5}')"
else
  echo "โŒ Backup error"
  exit 1
fi
chmod +x backup-volume.sh
./backup-volume.sh test_data ./backups

Step 4: Restore script

Create restore-volume.sh:

#!/bin/bash
# restore-volume.sh - Restore Docker volume

BACKUP_FILE=${1:?Usage: ./restore-volume.sh FILE.tar.gz VOLUME_NAME}
VOLUME_NAME=${2:?Usage: ./restore-volume.sh FILE.tar.gz VOLUME_NAME}

if [ ! -f "$BACKUP_FILE" ]; then
  echo "โŒ File not found: $BACKUP_FILE"
  exit 1
fi

echo "โš ๏ธ  This will overwrite volume '$VOLUME_NAME'"
read -p "Continue? (y/N) " confirm
[ "$confirm" != "y" ] && exit 0

# Create volume if it doesn't exist
docker volume create "$VOLUME_NAME" 2>/dev/null

echo "Restoring '$BACKUP_FILE' to '$VOLUME_NAME'..."

docker run --rm \
  -v "$VOLUME_NAME":/target \
  -v "$(pwd)":/backup \
  alpine sh -c "rm -rf /target/* && tar xzf /backup/$(basename $BACKUP_FILE) -C /target"

if [ $? -eq 0 ]; then
  echo "โœ… Restore completed"
else
  echo "โŒ Restore error"
  exit 1
fi

Step 5: Test restore

# Delete original volume
docker volume rm test_data

# Restore from backup
./restore-volume.sh backups/test_data_*.tar.gz test_data_restored

# Verify
docker run --rm -v test_data_restored:/data alpine cat /data/file.txt

Step 6: PostgreSQL backup (best practice)

For databases, use pg_dump before compressing:

#!/bin/bash
# backup-postgres.sh

CONTAINER=${1:-postgres}
BACKUP_DIR=./backups
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

docker exec $CONTAINER pg_dumpall -U postgres | gzip > "$BACKUP_DIR/postgres_$TIMESTAMP.sql.gz"

echo "โœ… Backup: $BACKUP_DIR/postgres_$TIMESTAMP.sql.gz"

Restore:

gunzip -c backups/postgres_*.sql.gz | docker exec -i postgres psql -U postgres

Step 7: Automatic backup (cron)

# Edit crontab
crontab -e

# Daily backup at 3am
0 3 * * * /opt/scripts/backup-volume.sh postgres_data /opt/backups >> /var/log/backup.log 2>&1

# Backup every 6 hours
0 */6 * * * /opt/scripts/backup-postgres.sh postgres >> /var/log/backup.log 2>&1

Step 8: Backup rotation

Keep only the last N backups:

# Delete backups older than 7 days
find /opt/backups -name "*.tar.gz" -mtime +7 -delete

# Keep only last 5
ls -t /opt/backups/*.tar.gz | tail -n +6 | xargs rm -f

Complete script with rotation

#!/bin/bash
# backup-with-rotation.sh

VOLUME=$1
BACKUP_DIR=/opt/backups
KEEP=7

./backup-volume.sh "$VOLUME" "$BACKUP_DIR"

# Delete old ones
ls -t "$BACKUP_DIR/${VOLUME}_"*.tar.gz 2>/dev/null | tail -n +$((KEEP+1)) | xargs rm -f

echo "Current backups:"
ls -lh "$BACKUP_DIR/${VOLUME}_"*.tar.gz

Verify integrity

# Verify tar is valid
tar tzf backup.tar.gz > /dev/null && echo "โœ… Valid file" || echo "โŒ Corrupt"

# View contents without extracting
tar tzf backup.tar.gz | head -20

Next step

โ†’ CI/CD with GitHub Actions