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