Problema

En un homelab típico se ejecutan varios motores de bases de datos (PostgreSQL, MySQL, MongoDB, etc.) y contenedores que gestionan volúmenes críticos (archivos de configuración, datos de aplicaciones, backups de máquinas virtuales). Mantener copias de seguridad consistentes, con retención flexible y almacenamiento fuera del nodo, suele ser un dolor de cabeza. Los síntomas más comunes son:

  • Pérdida de datos después de una actualización del contenedor.
  • Backups que fallan silenciosamente porque la credencial del bucket expiró.
  • Retención inadecuada que llena el disco local en pocos días.
  • Falta de un proceso de restauración probado, lo que convierte cualquier desastre en tiempo de inactividad prolongado.

El reto no es solo generar el dump, sino orquestar la tarea, almacenar los artefactos de forma segura y validar que la restauración funciona bajo cualquier runtime (Docker, Podman, UnraidOS).

Causa

  1. Configuración estática de credenciales
    Variables de entorno con claves de acceso a S3 o Azure se guardan en archivos de texto plano dentro del repositorio. Cuando el token expira, el job de backup se detiene sin generar logs claros.

  2. Políticas de retención mal definidas
    Usar “keep all” o “retain 30 days” sin considerar el tamaño de los dumps lleva rápidamente a un llenado del disco y a fallos de escritura.

  3. Incompatibilidad entre runtimes
    Un contenedor preparado para Docker puede fallar en Podman por diferencias en los volúmenes montados o en la gestión de permisos de usuario.

  4. Ausencia de pruebas de restauración
    La mayoría de los equipos confían en que el backup “funciona” porque el proceso de dump no muestra errores. Sin una restauración periódica, los dumps corruptos pasan desapercibidos.

  5. Dependencia de un único punto de almacenamiento
    Guardar los archivos solo en el nodo local elimina la ventaja de la recuperación ante desastres; cualquier fallo del hardware implica pérdida total.

Solución

Una arquitectura modular basada en contenedores permite separar la generación del backup, el transporte a storage y la gestión de retención. Los pasos clave son:

  1. Crear un contenedor de backup genérico
    Utilizar una imagen ligera (alpine + clientes de S3, Azure CLI, gsutil) que ejecute scripts para cada motor de base de datos. Cada script debe:

    • Detener brevemente la escritura (por ejemplo, pg_dump con --no-sync o mysqldump --single-transaction).
    • Comprimir el dump (gzip o zstd).
    • Añadir metadatos de timestamp y nombre del proyecto.
  2. Externalizar credenciales
    Almacenar claves en un secret manager (Docker secrets, HashiCorp Vault o archivos montados con permisos 0600). El contenedor lee la variable STORAGE_URL y el token desde /run/secrets.

  3. Definir políticas de retención con GFS (Grandfather‑Father‑Son)
    Un pequeño script en el contenedor evalúa la edad de los archivos y elimina los que no encajan en la regla:

    • Diarios → 7 días
    • Semanales → 4 semanas
    • Mensuales → 12 meses
    • Anuales → 3 años
  4. Orquestar la ejecución con Docker Compose o Kubernetes CronJob
    Cada backup se declara como un servicio restart: "no" y se programa con cron dentro del contenedor o con la capa de orquestación. La definición incluye:

    • Volumenes de origen (/var/lib/postgresql/data, /var/lib/mysql, /data/app).
    • Volumen de salida (/backups) montado en un host o en un contenedor de storage (minio, rclone).
  5. Implementar pruebas de restauración automáticas
    Un job separado extrae el último backup, lo descomprime y ejecuta un restore --dry-run. Si la prueba falla, se dispara una alerta (Discord, Slack, Telegram).

  6. Monitoreo y alertas
    Exportar métricas de éxito/fallo a Prometheus y crear reglas de alerta para:

    • Backup fallido > 1 vez.
    • Retención excede 80 % del disco.
    • Credenciales caducadas.

Cuándo aplicar esta solución

  • Entornos con más de una base de datos: la arquitectura modular permite añadir scripts sin tocar la lógica central.
  • Uso de varios runtimes: al encapsular la lógica en un contenedor, la misma imagen funciona tanto en Docker como en Podman.
  • Necesidad de retención a largo plazo: GFS integrado evita el crecimiento descontrolado.
  • Políticas de seguridad estrictas: secret manager y auditoría de logs cumplen con requisitos de compliance.

No es adecuada cuando:

  • Solo se necesita un backup puntual y manual; la sobrecarga de orquestación resulta innecesaria.
  • El hardware no soporta contenedores (por ejemplo, routers con firmware limitado).

Código

version: "3.9"

services:
  backup:
    image: alpine:3.20
    container_name: homelab-backup
    restart: "no"
    environment:
      - STORAGE_URL=s3://my-backup-bucket
    secrets:
      - s3_access_key
      - s3_secret_key
    volumes:
      - pg_data:/var/lib/postgresql/data:ro
      - mysql_data:/var/lib/mysql:ro
      - app_vol:/data/app:ro
      - backups:/backups
    command: >
      /bin/sh -c "
        /scripts/pg_dump.sh && 
        /scripts/mysql_dump.sh && 
        /scripts/app_vol_backup.sh && 
        /scripts/retention_gfs.sh
      "

  minio:
    image: minio/minio
    container_name: minio
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    command: server /data
    ports:
      - "9000:9000"
    volumes:
      - minio_data:/data

secrets:
  s3_access_key:
    file: ./secrets/s3_access_key.txt
  s3_secret_key:
    file: ./secrets/s3_secret_key.txt

volumes:
  pg_data:
    external: true
  mysql_data:
    external: true
  app_vol:
    external: true
  backups:
    driver: local
  minio_data:

Verificación

  1. Comprobar la existencia del archivo
    ls -lh backups/$(date +%Y-%m-%d)*.gz
    
  2. Validar integridad
    gunzip -c backups/$(date +%Y-%m-%d)_pg.dump.gz | pg_restore --list
    
  3. Ejecutar restauración de prueba
    docker exec -i postgres_container pg_restore -U postgres -d testdb < <(gunzip -c backups/$(date +%Y-%m-%d)_pg.dump.gz)
    
  4. Revisar métricas
    Acceder a http://localhost:9090/graph y buscar la serie backup_success_total.

Si los pasos anteriores generan resultados sin errores, la cadena de backup está operativa.

Notas adicionales

  • Rotación de credenciales: programa una rotación mensual y actualiza los secrets con docker secret rm y docker secret create.
  • Compatibilidad con Podman: reemplaza docker por podman en los comandos; la sintaxis del compose es idéntica si usas podman-compose.
  • Almacenamiento en Azure o GCS: solo cambia la variable STORAGE_URL a az://container o gs://bucket y asegura que el contenedor tenga azcopy o gsutil instalados.
  • Escalado: para grandes volúmenes, considera dividir los dumps por tabla y paralelizar la compresión con pigz.
  • Auditoría: habilita --log-file en cada script y envía los logs a un syslog central para cumplir con requisitos de auditoría.