Ir al contenido

Automatizar backups incremental de volúmenes Docker a almacenamiento remoto con duplicity

Rogelio Guerra Riverón
Autor
Rogelio Guerra Riverón
Construyendo mi propia infraestructura web desde cero. Aquí documento cada paso: servidores, redes, contenedores y lo que vaya surgiendo.

El problema
#

Tengo varios contenedores Docker con datos críticos en volúmenes. Un día se corrompió una base de datos y perdí días de información. Después de eso decidí automatizar los backups incrementales a almacenamiento remoto. Aquí comparto cómo lo hice.

Preparación
#

Primero instalé duplicity en el servidor anfitrión:

apt-get install duplicity python3-pip
pip3 install boto3  # si usas S3

Necesitaba acceso a almacenamiento remoto. En mi caso usé B2 de Backblaze, que es económico. También puedes usar S3, Google Cloud Storage o incluso un servidor SFTP propio.

Generé las credenciales necesarias y las guardé en un archivo seguro:

cat > /root/.duplicity_env << 'EOF'
export B2_ACCOUNT_ID="tu_account_id"
export B2_APP_KEY="tu_app_key"
EOF

chmod 600 /root/.duplicity_env

Estructura de backups
#

Decidí hacer backup de los volúmenes por separado. Para un contenedor con PostgreSQL, primero detenía el contenedor para garantizar consistencia:

docker stop mi_postgres

Luego ejecutaba duplicity:

source /root/.duplicity_env

duplicity \
  --full-if-older-than 30D \
  --include=/var/lib/docker/volumes/postgres_data/_data \
  --exclude='**' \
  /var/lib/docker/volumes/postgres_data/_data \
  b2://bucket_name/postgres_backup

docker start mi_postgres

Este comando hace backups completos cada 30 días e incrementales el resto del tiempo. Sin --full-if-older-than, los incrementales se acumulan y la restauración es más lenta.

Automatización con cron
#

Creé un script de backup completo:

cat > /usr/local/bin/backup-docker-volumes.sh << 'EOF'
#!/bin/bash
source /root/.duplicity_env

TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/docker-backups-$TIMESTAMP.log"

backup_volume() {
  local volume_name=$1
  local b2_path=$2
  local container_name=$3
  
  echo "[$(date)] Iniciando backup de $volume_name" >> $LOG_FILE
  
  docker stop $container_name 2>/dev/null
  sleep 5
  
  duplicity \
    --full-if-older-than 30D \
    --include=/var/lib/docker/volumes/${volume_name}/_data \
    --exclude='**' \
    /var/lib/docker/volumes/${volume_name}/_data \
    b2://$b2_path >> $LOG_FILE 2>&1
  
  docker start $container_name
  echo "[$(date)] Backup de $volume_name completado" >> $LOG_FILE
}

backup_volume "postgres_data" "mi_bucket/postgres" "postgres"
backup_volume "appdata" "mi_bucket/app" "webapp"

duplicity cleanup --force b2://mi_bucket/postgres 2>/dev/null
duplicity cleanup --force b2://mi_bucket/app 2>/dev/null
EOF

chmod +x /usr/local/bin/backup-docker-volumes.sh

Lo agregué a crontab para que corra cada noche:

0 2 * * * /usr/local/bin/backup-docker-volumes.sh

Restauración ante fallos
#

Cuando un contenedor falla, la restauración es sencilla:

# Crear directorio temporal
mkdir -p /tmp/restore
cd /tmp/restore

source /root/.duplicity_env

# Restaurar desde b2
duplicity restore b2://mi_bucket/postgres .

# Los datos están en /tmp/restore
ls -la

Luego puedo copiar los datos al volumen:

docker stop postgres
cp -r /tmp/restore/* /var/lib/docker/volumes/postgres_data/_data/
docker start postgres

Verificación
#

Ejecuto verificaciones mensuales:

duplicity verify b2://mi_bucket/postgres /var/lib/docker/volumes/postgres_data/_data

Esto comprueba que los backups sean válidos sin restaurarlos completamente.

Lecciones aprendidas
#

  • Los backups sin verificación no sirven. He encontrado problemas en restauraciones de prueba.
  • Detener el contenedor antes de backup es crítico para bases de datos.
  • Los backups incrementales después de 30 días se vuelven difíciles de restaurar. Por eso limito el intervalo de completos.
  • Guarda credenciales con chmod 600 siempre.

Esta configuración me ha salvado varias veces. La automatización es la clave: sin cron, nunca hubiera tenido backups consistentes.