El problema#
Tenía varios contenedores Docker corriendo en mi servidor doméstico con datos que no podía perder. Los backups manuales no escalan, así que decidí automatizar todo con rsync y cron. Aquí documento lo que funcionó.
La arquitectura#
Mi setup:
- Servidor local: Host Docker con volúmenes en
/var/lib/docker/volumes/ - Almacenamiento remoto: NAS en red con SSH habilitado
- Herramientas: rsync para sincronización incremental, cron para programación, sha256sum para verificación
Preparación#
Primero, configura acceso SSH sin contraseña desde el host Docker al NAS:
ssh-keygen -t ed25519 -f ~/.ssh/backup_key -N ""
ssh-copy-id -i ~/.ssh/backup_key usuario@nas.localVerifica que funciona:
ssh -i ~/.ssh/backup_key usuario@nas.local "ls -la"Script de backup incremental#
Crea /usr/local/bin/docker-backup.sh:
#!/bin/bash
BACKUP_USER="usuario"
BACKUP_HOST="nas.local"
BACKUP_PATH="/mnt/backups/docker"
SSH_KEY="/root/.ssh/backup_key"
LOG_FILE="/var/log/docker-backup.log"
MANIFEST="/var/log/docker-backup-manifest.txt"
# Volúmenes a respaldar (ajusta según tus necesidades)
VOLUMES=("postgres_data" "nextcloud_data" "app_config")
# Función para logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
log "=== Iniciando backup incremental ==="
# Crear directorio remoto si no existe
ssh -i "$SSH_KEY" "$BACKUP_USER@$BACKUP_HOST" "mkdir -p $BACKUP_PATH"
# Función para respaldar volumen
backup_volume() {
local volume=$1
local volume_path="/var/lib/docker/volumes/${volume}/_data"
local remote_path="$BACKUP_USER@$BACKUP_HOST:$BACKUP_PATH/$volume"
if [ ! -d "$volume_path" ]; then
log "ERROR: Volumen $volume no encontrado en $volume_path"
return 1
fi
log "Respaldando volumen: $volume"
# rsync incremental
rsync -av --delete \
-e "ssh -i $SSH_KEY" \
"$volume_path/" \
"$remote_path/" >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
log "✓ Backup completado: $volume"
return 0
else
log "✗ Error en backup: $volume"
return 1
fi
}
# Respaldar todos los volúmenes
FAILED=0
for volume in "${VOLUMES[@]}"; do
backup_volume "$volume" || ((FAILED++))
done
# Generar manifiesto con checksums
log "Generando manifiesto de integridad..."
{
echo "Manifiesto de Backup - $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
for volume in "${VOLUMES[@]}"; do
echo "Volumen: $volume"
find "/var/lib/docker/volumes/${volume}/_data" -type f -exec sha256sum {} \; 2>/dev/null
echo ""
done
} > "$MANIFEST"
# Respaldar el manifiesto también
scp -i "$SSH_KEY" "$MANIFEST" "$BACKUP_USER@$BACKUP_HOST:$BACKUP_PATH/manifest-$(date +%Y%m%d).txt"
if [ $FAILED -eq 0 ]; then
log "=== Backup completado exitosamente ==="
exit 0
else
log "=== Backup completado con $FAILED errores ==="
exit 1
fiHazlo ejecutable:
chmod +x /usr/local/bin/docker-backup.shVerificación de integridad#
Crea /usr/local/bin/docker-backup-verify.sh:
#!/bin/bash
BACKUP_USER="usuario"
BACKUP_HOST="nas.local"
BACKUP_PATH="/mnt/backups/docker"
SSH_KEY="/root/.ssh/backup_key"
LOG_FILE="/var/log/docker-backup-verify.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
log "=== Iniciando verificación de integridad ==="
# Descargar manifiesto más reciente
LATEST_MANIFEST=$(ssh -i "$SSH_KEY" "$BACKUP_USER@$BACKUP_HOST" \
"ls -t $BACKUP_PATH/manifest-*.txt | head -1")
if [ -z "$LATEST_MANIFEST" ]; then
log "ERROR: No se encontró manifiesto remoto"
exit 1
fi
scp -i "$SSH_KEY" "$BACKUP_USER@$BACKUP_HOST:$LATEST_MANIFEST" /tmp/manifest-verify.txt
# Extraer y verificar checksums
while IFS= read -r line; do
if [[ $line =~ ^[a-f0-9]{64} ]]; then
hash=$(echo "$line" | awk '{print $1}')
file=$(echo "$line" | awk '{print $2}')
if [ -f "$file" ]; then
local_hash=$(sha256sum "$file" | awk '{print $1}')
if [ "$hash" != "$local_hash" ]; then
log "ALERTA: Checksum inconsistente en $file"
fi
fi
fi
done < /tmp/manifest-verify.txt
log "=== Verificación completada ==="Programación con cron#
Edita el crontab:
crontab -eAñade estas líneas:
# Backup diario a las 2:00 AM
0 2 * * * /usr/local/bin/docker-backup.sh
# Verificación semanal los domingos a las 3:00 AM
0 3 * * 0 /usr/local/bin/docker-backup-verify.sh
# Enviar log por correo si hay errores
0 4 * * * [ -f /var/log/docker-backup.log ] && tail -20 /var/log/docker-backup.log | mail -s "Backup Docker - Resumen" admin@example.comMonitoreo#
Revisa los logs regularmente:
tail -f /var/log/docker-backup.logPara investigar problemas de rsync específicos:
rsync -av --dry-run /var/lib/docker/volumes/postgres_data/_data usuario@nas.local:/mnt/backups/docker/postgres_data/Resultado#
Ahora tengo backups incrementales automáticos cada noche. rsync solo transfiere cambios, lo que ahorra ancho de banda. La verificación de integridad me alerta si algo se corrompe. Es simple, func