Tienes copias de seguridad. Incluso tienes una marca verde en algún panel. Luego un nodo muere, el on-call inicia una restauración,
y de repente lo único que estás restaurando es tu respeto por la Ley de Murphy.
Docker facilita desplazar aplicaciones. También facilita olvidar dónde vive realmente la información: volúmenes, bind mounts,
secretos, archivos env, registros y un par de directorios “temporales” que alguien codificó a las 2 a.m.
Un simulacro de restauración es un producto, no un ritual
Una “copia de seguridad” es una promesa. Un simulacro de restauración es donde amortizas esa promesa y demuestras que puedes cumplirla bajo presión.
El entregable no es un tarball en almacenamiento de objetos. Es un proceso de recuperación repetible con límites temporales conocidos.
Tu simulacro de restauración tiene un trabajo: convertir suposiciones en mediciones. ¿Cuál es tu RPO (cuánto dato puedes perder)
y RTO (cuánto tiempo puedes estar caído)? ¿Qué partes son lentas? ¿Qué partes son frágiles? ¿Qué requiere la memoria y la cafeína
de una persona específica?
El resultado más valioso de un simulacro suele ser aburrido: una lista de archivos faltantes, permisos erróneos, secretos no localizables
y sorpresas del tipo “pensamos que esto estaba en la copia”. Aburrido es bueno. Aburrido es cómo sobrevives a los incidentes.
Una cita para tener en tu escritorio: La esperanza no es una estrategia.
(atribuido al Gral. Gordon R. Sullivan)
Qué estás restaurando realmente en Docker
Docker no “contiene” el estado. Solo facilita extraviar el estado. Para los simulacros de restauración, trata tu sistema en capas:
estado del host, estado del contenedor, estado de datos y estado de despliegue. Luego decide qué prometes restaurar.
1) Estado de datos
- Volúmenes nombrados (gestionados por Docker): normalmente bajo
/var/lib/docker/volumes. - Bind mounts: en cualquier lugar del sistema de archivos del host; a menudo no están en la misma política de backup que los volúmenes.
- Almacenamiento externo: NFS, iSCSI, Ceph, EBS, LUNs SAN, datasets ZFS, LVM, etc.
- Bases de datos: Postgres/MySQL/Redis/Elastic/etc. El método de respaldo importa más que su ubicación.
2) Estado de despliegue
- Archivos Compose, archivos de entorno y overrides.
- Secretos y su mecanismo de entrega (Swarm secrets, archivos, SOPS, plantillas de Vault, etc.).
- Etiquetas de imagen: “latest” no es un plan de restauración.
- Acceso al registro: si no puedes hacer pull, no puedes arrancar.
3) Estado del host
- Configuración de Docker Engine, driver de almacenamiento, flags del daemon.
- Detalles del kernel y sistema de archivos: expectativas de overlay2, xfs ftype, SELinux/AppArmor.
- Red: reglas de firewall, DNS, rutas, MTU.
4) Estado de runtime (generalmente no vale la pena “restaurar”)
Las capas de contenedor y archivos efímeros de runtime pueden recrearse. Si estás haciendo backup de todo el directorio raíz de Docker
(/var/lib/docker) esperando resucitar contenedores byte a byte, te estás inscribiendo para fallos sutiles.
El objetivo correcto casi siempre es volúmenes de datos más configuración de despliegue, y reconstruir contenedores de forma limpia.
Broma #1: Si tu plan de recuperación comienza con “creo que los datos están en ese nodo”, felicitaciones: has inventado un punto único de sorpresa.
Hechos y contexto histórico (para que dejes de repetirlo)
- Hecho 1: La era temprana de Docker con AUFS normalizó la idea de que los contenedores son desechables; muchos equipos equivocadamente hicieron los datos desechables también.
- Hecho 2: El cambio de AUFS a overlay2 no fue solo por rendimiento: cambiaron las semánticas de restauración y los requisitos del sistema de archivos (notablemente las expectativas de XFS
ftype=1). - Hecho 3: El movimiento de la industria hacia “infraestructura inmutable” redujo las restauraciones de host pero aumentó la necesidad de restaurar el estado externalizado (volúmenes, stores de objetos, bases de datos gestionadas).
- Hecho 4: Compose se convirtió en la descripción de aplicaciones por defecto para muchas organizaciones, incluso cuando la rigurosidad operativa (rotación de secretos, versiones fijadas, healthchecks) no acompañó.
- Hecho 5: Muchos incidentes atribuidos a “Docker” son en realidad problemas de coherencia de almacenamiento: copias consistentes a nivel de crash tomadas por debajo de una base de datos activa.
- Hecho 6: El ransomware cambió la estrategia de backups de “podemos restaurar?” a “podemos restaurar sin confiar en que el atacante no cifró nuestras claves de backup?”
- Hecho 7: Los registros de imágenes se volvieron infraestructura crítica; perder un registro privado o sus credenciales puede bloquear restauraciones aunque los datos estén a salvo.
- Hecho 8: Los snapshots de sistemas de archivos (LVM/ZFS) facilitaron backups rápidos—pero también fomentaron la excesiva confianza cuando las aplicaciones no eran seguras para snapshots.
- Hecho 9: El auge de contenedores sin root cambió las rutas de backup y los modelos de permisos; restaurar datos como root puede romper silenciosamente runtimes rootless más adelante.
Elige el alcance del simulacro: host, app o capa de datos
Un simulacro de restauración puede ser tres cosas distintas. Si no declaras cuál vas a hacer, “tendrás éxito” en el más fácil
y fallarás en el que importa.
Alcance A: Simulacro de restauración de datos (el más común y valioso)
Restauras volúmenes/bind-mounts y redeployas contenedores desde imágenes y configuraciones conocidas. Esta es la opción por defecto correcta
para la mayoría de los entornos de producción con Docker Compose.
Alcance B: Simulacro de restauración de la app (despliegue + datos)
Restauras la pila exacta de la aplicación: archivos Compose, env/secretos, proxy inverso, certificados, además de los datos. Esto valida
la suposición de “todo lo necesario para ejecutar”. También expone la enfermedad de “guardamos esa configuración en el portátil de alguien”.
Alcance C: Simulacro de reconstrucción del host (raro, pero hazlo al menos anualmente)
Asumes que el nodo se perdió. Provisionas un host nuevo y restauras sobre él. Aquí descubrirás dependencias de kernels antiguos, paquetes faltantes,
reglas iptables personalizadas, hacks extraños de MTU y desajustes de driver de almacenamiento.
Guía rápida de diagnóstico (encuentra el cuello de botella rápido)
Durante una restauración, típicamente estás bloqueado por una de cuatro cosas: identidad/credenciales, integridad de datos,
velocidad de transferencia o correctitud de la aplicación. No adivines. Triagia en este orden.
Primero: ¿Puedes acceder siquiera a lo que necesitas?
- ¿Tienes las credenciales del repositorio de backups y las claves de cifrado?
- ¿Puede el host de restauración alcanzar el object storage / servidor de backups / NAS?
- ¿Puedes hacer pull de imágenes de contenedor (o tienes una caché air-gapped)?
Segundo: ¿El backup está completo y es internamente consistente?
- ¿Tienes todas las rutas de volúmenes/bind-mount esperadas para la app?
- ¿Coinciden las sumas de verificación? ¿Puedes listar y extraer archivos?
- Para bases de datos: ¿tienes un backup lógico o solo una copia de sistema de archivos crash-consistente?
Tercero: ¿Dónde se va el tiempo?
- ¿Rendimiento de red (egreso de object storage, restricciones VPN, throttling)?
- ¿Descompresión y crypto (herramientas de restauración single-threaded)?
- ¿IOPS y tormentas de restauración de archivos pequeños (millones de archivos pequeños)?
Cuarto: ¿Por qué no arranca la app?
- Permisos/propiedad/etiquetas SELinux en los datos restaurados.
- Divergencia de configuración: env vars, secretos, etiquetas de imagen cambiadas.
- Incompatibilidad de esquema: restaurar datos antiguos en una versión nueva de la app.
Si solo recuerdas una cosa: mide la velocidad de transferencia y verifica las claves temprano. Todo lo demás es secundario.
Construye un entorno de restauración realista
Un simulacro de restauración en el mismo host que produjo las copias es una mentira reconfortante. Comparte las mismas imágenes en caché,
las mismas credenciales ya logueadas y las mismas reglas de firewall afinadas a mano. Tu objetivo es fallar honestamente.
Qué significa “realista”
- Host limpio: nueva VM o hardware, misma familia de OS, mismas versiones mayores.
- Mismas restricciones de red: misma ruta a almacenamiento de backups, mismo NAT/VPN, mismo DNS.
- Sin estado oculto: no reutilices
/var/lib/dockerantiguo; no montes volúmenes de producción directamente. - Con límite de tiempo: estás probando RTO; deja de admirar los logs y empieza un cronómetro.
Define criterios de éxito desde el principio
- RPO validado: puedes señalar el backup más reciente exitoso y mostrar su marca de tiempo y contenido.
- RTO medido: desde “host provisionado” hasta “servicio responde correctamente”.
- Correctitud verificada: no solo “los contenedores están en marcha” sino “los datos son correctos”.
Tareas prácticas: comandos, salidas, decisiones
Estas son tareas de simulacro que espero ver en un runbook. Cada una incluye un comando, qué significa su salida y
la decisión que tomas a partir de ella. Ejecútalas en el host objetivo de restauración salvo que se indique otra cosa.
Tarea 1: Inventario de contenedores en ejecución y sus montajes (entorno de origen)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
NAMES IMAGE STATUS
api registry.local/api:1.42.0 Up 3 days
postgres postgres:15 Up 3 days
nginx nginx:1.25 Up 3 days
Significado: Esta es la lista mínima de “qué existe”. No es suficiente, pero es un comienzo.
Decisión: Identifica qué contenedores son con estado (aquí: postgres) y cuáles son sin estado.
cr0x@server:~$ docker inspect postgres --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume pgdata -> /var/lib/postgresql/data
bind /srv/postgres/conf -> /etc/postgresql
Significado: Tienes tanto un volumen nombrado como un bind mount. Dos políticas de backup, dos modos de falla.
Decisión: Tu plan de restauración debe capturar tanto pgdata como /srv/postgres/conf.
Tarea 2: Listar volúmenes Docker y mapearlos a proyectos
cr0x@server:~$ docker volume ls
DRIVER VOLUME NAME
local myapp_pgdata
local myapp_redisdata
local shared_uploads
Significado: Los nombres de volumen suelen codificar nombres de proyecto Compose. Eso es útil durante restauraciones.
Decisión: Decide qué volúmenes son críticos y cuáles pueden reconstruirse (p. ej., caches).
Tarea 3: Identificar dónde viven los volúmenes en disco (host de restauración)
cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker
Significado: Directorio raíz por defecto de Docker. Los volúmenes estarán bajo esta ruta a menos que se configure otra cosa.
Decisión: Confirma que esto coincide con tus expectativas de backup; las discrepancias causan “restauración exitosa, datos faltantes.”
Tarea 4: Verificar sistema de archivos y espacio libre antes de restaurar
cr0x@server:~$ df -hT /var/lib/docker /srv
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda2 ext4 200G 32G 158G 17% /
/dev/sdb1 xfs 800G 120G 680G 15% /srv
Significado: Tienes margen de capacidad. También nota tipos de sistema de archivos; algunos comportamientos difieren para overlay y permisos.
Decisión: Si el espacio disponible es escaso, no “intentes de todas formas.” Redimensiona primero o elige un objetivo de restauración más grande.
Tarea 5: Confirmar driver de almacenamiento Docker y compatibilidad de kernel
cr0x@server:~$ docker info --format 'Driver={{.Driver}}; BackingFS={{.BackingFilesystem}}'
Driver=overlay2; BackingFS=extfs
Significado: overlay2 sobre ext4 (Docker reporta “extfs”). Si tu host original usó un driver distinto, no asumas portabilidad de /var/lib/docker.
Decisión: Prefiere restaurar solo volúmenes y configuración; reconstruye contenedores desde imágenes.
Tarea 6: Verificar que el artefacto de backup existe y es reciente
cr0x@server:~$ ls -lh /backups/myapp/
total 4.1G
-rw------- 1 root root 1.9G Jan 2 01:05 myapp-volumes-2026-01-02.tar.zst
-rw------- 1 root root 2.2G Jan 2 01:06 myapp-bindmounts-2026-01-02.tar.zst
-rw------- 1 root root 12K Jan 2 01:06 myapp-compose-2026-01-02.tgz
Significado: Artefactos separados para volúmenes, bind mounts y configuración de despliegue es saludable. Permite restauraciones parciales.
Decisión: Si el archivo más reciente es más antiguo que tu RPO, detente y escala. Restaurar datos obsoletos sin avisar es cómo los incidentes se vuelven carreras.
Tarea 7: Validar integridad del archivo antes de extraer
cr0x@server:~$ zstd -t /backups/myapp/myapp-volumes-2026-01-02.tar.zst
/backups/myapp/myapp-volumes-2026-01-02.tar.zst: OK
Significado: El stream comprimido no está corrupto.
Decisión: Si esto falla, no extraigas “algo de él.” Localiza otro set de backups o rehace la tubería de backups.
Tarea 8: Listado en seco de archivos dentro del backup (buscar rutas faltantes)
cr0x@server:~$ tar -I zstd -tf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst | head
srv/postgres/conf/postgresql.conf
srv/postgres/conf/pg_hba.conf
srv/myapp/env/.env.production
srv/nginx/conf.d/app.conf
Significado: Puedes ver archivos de configuración bind-mounted esperados.
Decisión: Si faltan directorios clave, detente y corrige la definición de backup. Los simulacros de restauración no son trucos de magia.
Tarea 9: Restaurar bind mounts primero a un prefijo de staging (evita sobreescribir)
cr0x@server:~$ mkdir -p /restore-staging
cr0x@server:~$ tar -I zstd -xpf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst -C /restore-staging
cr0x@server:~$ ls -la /restore-staging/srv/postgres/conf
total 24
drwxr-xr-x 2 root root 4096 Jan 2 01:06 .
drwxr-xr-x 3 root root 4096 Jan 2 01:06 ..
-rw-r--r-- 1 root root 980 Jan 2 01:06 pg_hba.conf
-rw-r--r-- 1 root root 3150 Jan 2 01:06 postgresql.conf
Significado: Archivos restaurados conservando permisos (-p). La propiedad importará más tarde.
Decisión: Compara staging con el layout objetivo. Solo entonces mueve a su lugar.
Tarea 10: Restaurar datos de volúmenes nombrados usando un contenedor helper
Para volúmenes nombrados, no copies a mano dentro de los internos de Docker. Usa un contenedor temporal que monte el volumen.
cr0x@server:~$ docker volume create myapp_pgdata
myapp_pgdata
cr0x@server:~$ docker run --rm -v myapp_pgdata:/data -v /backups/myapp:/backup alpine:3.20 sh -c "cd /data && tar -I zstd -xpf /backup/myapp-volumes-2026-01-02.tar.zst --strip-components=2 ./volumes/myapp_pgdata"
tar: removing leading './' from member names
Significado: Estás extrayendo solo el subárbol para ese volumen dentro de la ruta montada del volumen.
Decisión: Si el layout del archivo no coincide con lo esperado, detente y revisa el script de backup; no improvises restauraciones parciales.
Tarea 11: Comprobación de cordura del contenido y la propiedad del volumen restaurado
cr0x@server:~$ docker run --rm -v myapp_pgdata:/data alpine:3.20 sh -c "ls -la /data | head"
total 128
drwx------ 19 999 999 4096 Jan 2 01:04 .
drwxr-xr-x 1 root root 4096 Jan 2 02:10 ..
-rw------- 1 999 999 3 Jan 2 01:04 PG_VERSION
drwx------ 5 999 999 4096 Jan 2 01:04 base
Significado: La propiedad es 999:999, típico de la imagen oficial de Postgres. Bien.
Decisión: Si la propiedad es incorrecta (p. ej., root), arréglalo ahora (chown) o Postgres puede negarse a arrancar.
Tarea 12: Restaurar configuración de despliegue y fijar versiones de imágenes
cr0x@server:~$ mkdir -p /opt/myapp
cr0x@server:~$ tar -xpf /backups/myapp/myapp-compose-2026-01-02.tgz -C /opt/myapp
cr0x@server:~$ ls -la /opt/myapp
total 40
drwxr-xr-x 3 root root 4096 Jan 2 02:13 .
drwxr-xr-x 3 root root 4096 Jan 2 02:13 ..
-rw-r--r-- 1 root root 2241 Jan 2 01:06 docker-compose.yml
-rw------- 1 root root 412 Jan 2 01:06 .env.production
Significado: La configuración está presente, incluido el archivo env. Trátalo como sensible.
Decisión: Asegura que las imágenes estén fijadas a etiquetas o digests que confíes. Si el Compose usa latest, arréglalo como parte del simulacro.
Tarea 13: Validar que se puedan descargar las imágenes (o que ya estén disponibles)
cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml pull
[+] Pulling 3/3
✔ postgres Pulled
✔ api Pulled
✔ nginx Pulled
Significado: Tu ruta de registro, credenciales y red son funcionales.
Decisión: Si los pulls fallan, tu plan de restauración debe incluir un mirror del registro, tarballs de imágenes offline o pasos de recuperación de credenciales.
Tarea 14: Levantar la pila y vigilar fallos rápidos
cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml up -d
[+] Running 3/3
✔ Container myapp-postgres-1 Started
✔ Container myapp-api-1 Started
✔ Container myapp-nginx-1 Started
Significado: Los contenedores arrancaron, pero esto no prueba correctitud.
Decisión: Revisa inmediatamente logs y endpoints de salud. “Started” aún puede significar “roto silenciosamente”.
Tarea 15: Verificar salud y leer los primeros errores, no los últimos
cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml ps
NAME IMAGE COMMAND SERVICE STATUS PORTS
myapp-api-1 registry.local/api:1.42.0 "gunicorn -c ..." api Up 20s (healthy) 0.0.0.0:8080->8080/tcp
myapp-nginx-1 nginx:1.25 "/docker-entrypoint…" nginx Up 20s 0.0.0.0:80->80/tcp
myapp-postgres-1 postgres:15 "docker-entrypoint…" postgres Up 20s (healthy) 5432/tcp
Significado: Las health checks están pasando. Es una señal fuerte, no una garantía.
Decisión: Si la salud falla, revisa los logs relevantes más tempranos (DB primero, luego app, luego proxy).
cr0x@server:~$ docker logs --tail=50 myapp-postgres-1
2026-01-02 02:14:12.101 UTC [1] LOG: database system is ready to accept connections
Significado: Postgres arrancó limpiamente. Si ves “invalid checkpoint record” o “permission denied”, tu restauración no es correcta.
Decisión: Para errores de BD, decide si necesitas una restauración lógica en lugar de una copia de sistema de archivos.
Tarea 16: Demostrar correctitud con una consulta a nivel de aplicación
cr0x@server:~$ curl -fsS http://127.0.0.1:8080/health
{"status":"ok","db":"ok","version":"1.42.0"}
Significado: Tu app dice que está saludable. Ahora valida los datos, no solo la vivacidad.
Decisión: Ejecuta una consulta conocida o un chequeo de negocio (p. ej., “existe un registro de cliente específico”).
cr0x@server:~$ docker exec -i myapp-postgres-1 psql -U postgres -tAc "select now(), count(*) from users;"
2026-01-02 02:14:35.812396+00|1842
Significado: Tienes datos y parecen plausibles.
Decisión: Compara con un rango esperado o un informe de checksums. Si el conteo es cero, restauraste lo incorrecto o apuntaste a un volumen vacío.
Tarea 17: Medir el rendimiento de restauración para predecir RTO
cr0x@server:~$ /usr/bin/time -f 'elapsed=%E cpu=%P' tar -I zstd -xpf /backups/myapp/myapp-volumes-2026-01-02.tar.zst -C /restore-staging-voltest
elapsed=0:02:41 cpu=380%
Significado: La CPU es un cuello de botella (380% indica ~4 núcleos ocupados). Bueno saberlo.
Decisión: Si la restauración está limitada por CPU, puedes reducir el RTO añadiendo cores, ajustando el nivel de compresión o cambiando herramientas—antes del incidente real.
Tarea 18: Verificar permisos y contexto SELinux/AppArmor (donde aplique)
cr0x@server:~$ getenforce
Enforcing
Significado: SELinux está activo. Los bind mounts pueden necesitar etiquetado, o los contenedores verán “permission denied” a pesar de permisos UNIX correctos.
Decisión: Si usas SELinux, asegúrate de que tu Compose use :z o :Z según corresponda, y relabel las rutas restauradas.
cr0x@server:~$ ls -lZ /srv/postgres/conf | head -n 3
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 pg_hba.conf
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 postgresql.conf
Significado: El tipo SELinux por defecto en los archivos de configuración puede no coincidir con lo que espera tu contenedor.
Decisión: Si los contenedores fallan al leer bind mounts bajo SELinux, debes relabel o ajustar opciones de montaje; no desactives SELinux como “solución”.
Tarea 19: Confirma que la restauración no intercambió volúmenes silenciosamente
cr0x@server:~$ docker inspect myapp-postgres-1 --format '{{range .Mounts}}{{.Name}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
myapp_pgdata /var/lib/docker/volumes/myapp_pgdata/_data -> /var/lib/postgresql/data
Significado: El contenedor está usando el volumen esperado.
Decisión: Si ves un nombre de volumen inesperado, estás restaurando en un volumen pero corriendo contra otro. Eso es una falla clásica en simulacros.
Tarea 20: Captura evidencia y tiempos (para que el simulacro mejore)
cr0x@server:~$ journalctl -u docker --since "today" | tail -n 5
Jan 02 02:13:55 server dockerd[1023]: API listen on /run/docker.sock
Jan 02 02:14:03 server dockerd[1023]: Loading containers: done.
Significado: Tienes marcas de tiempo para el arranque del daemon Docker y la carga de contenedores.
Decisión: Registra estas en el informe del simulacro junto con inicio/fin de restauración. Si no mides, discutirás durante el incidente en lugar de solucionarlo.
Broma #2: Un simulacro de restauración es como usar hilo dental—todos dicen que lo hacen, y la evidencia suele ser sangrienta.
Tres mini-historias corporativas (cómo falla esto en la vida real)
Mini-historia 1: El incidente causado por una suposición equivocada
Una compañía SaaS mediana ejecutaba Docker Compose en un par de VMs potentes. Sus backups eran “simples”: tar nocturno de
/srv más un snapshot semanal del disco de la VM. La suposición era que todo importante vivía en /srv.
El incidente comenzó con una falla de almacenamiento mundana. La VM no arrancó bien después de un incidente en el host. El equipo levantó
una nueva VM y restauró /srv desde el backup nocturno. Compose arrancó. Nginx sirvió páginas. La API devolvía 500s.
Los logs de Postgres mostraron que se había inicializado un clúster de base de datos vacío. Nadie lo había restaurado—porque nadie lo había respaldado.
La BD usaba un volumen nombrado de Docker, situado en la raíz de Docker bajo /var/lib/docker/volumes, fuera del alcance del backup.
El snapshot semanal de la VM lo contenía, pero era demasiado viejo para el RPO implícito de la compañía y vivía en un sistema diferente gestionado por otro equipo.
El postmortem no fue dramático. Fue peor: fue obvio. Habían confundido “nuestro directorio de datos de la app” con “dónde Docker guarda el estado”.
La solución no fue elegante tampoco: inventariar montajes, respaldar explícitamente volúmenes nombrados y ejecutar un simulacro trimestral en un host limpio.
También: deja de llamarlo “backups simples” si no incluye tu base de datos.
Mini-historia 2: La optimización que salió mal
Otra organización se tomó en serio la velocidad. Su tiempo de restauración era demasiado lento para la paciencia de liderazgo, así que optimizaron.
Pasaron de dumps lógicos de BD a snapshots crash-consistentes del volumen de la base de datos. Fue más rápido y produjo transferencias incrementales más pequeñas. Todos celebraron.
Seis meses después, necesitaron restaurar. Un despliegue malo corrompió el estado de la aplicación y revirtieron. La restauración “funcionó”
mecánicamente: el snapshot se extrajo, los contenedores arrancaron, las healthchecks se pusieron en verde. Luego el tráfico subió y la BD comenzó a lanzar
errores: corrupción sutil de índices, seguida por rarezas del planer de consultas, seguida por un crash loop.
La causa raíz fue aburrida pero mortal: el snapshot se tomó mientras la base de datos tenía carga de escritura, sin coordinar un checkpoint
ni usar un mecanismo de backup nativo de la BD. El backup del volumen fue consistente a nivel de sistema de archivos, no necesariamente a nivel de base de datos.
Restauró rápido y falló tarde—exactamente el tipo de fallo que consume más tiempo.
La corrección fue un compromiso: mantener snapshots rápidos para recuperaciones cortas “ups”, pero también tomar backups nativos periódicos de BD (o correr
el procedimiento de base backup soportado) que puedan validarse. Añadieron además un trabajo de verificación que inicia una BD restaurada en un sandbox
y ejecuta checks de integridad. Las optimizaciones están permitidas. Las optimizaciones no verificadas son solo riesgo con etiqueta de rendimiento.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Una compañía cercana a finanzas ejecutaba varios servicios orientados al cliente en Docker. Su líder SRE no era romántico. Cada trimestre, corrían
un simulacro de restauración usando una VPC aislada, una imagen de VM limpia y una copia del repositorio de backups. El simulacro tenía una lista de verificación y un cronómetro.
El simulacro siempre incluía los mismos pasos tediosos: verificar que las claves de cifrado sean accesibles para el on-call, validar manifiestos de backup, restaurar volúmenes
en staging primero, luego intercambiarlos en su lugar, luego ejecutar un puñado de checks de cordura a nivel de aplicación. Finalmente, documentar tiempos y actualizar el runbook.
A nadie le encantaba. Nadie lo ponía en una presentación.
Entonces llegó un incidente real: un error operativo borró un volumen de producción y se replicó rápidamente. El on-call siguió el runbook sin improvisar.
Ya sabían que el paso más lento era la descompresión y habían ajustado el tamaño del host de restauración para ello. Ya sabían exactamente qué secretos debían estar presentes y dónde vivían.
Ya habían lidiado con el etiquetado SELinux—en el simulacro, no durante el incidente.
La restauración terminó dentro de la ventana esperada. No porque el equipo fuera heroico, sino porque fueron aburridos a propósito. En ops, lo aburrido es una característica.
Listas de verificación / plan paso a paso
Plan de simulacro de restauración (repetible, no “veamos qué pasa”)
-
Declara alcance y criterios de éxito.
- ¿Qué servicios? ¿Qué conjuntos de datos? ¿Qué RPO/RTO estás validando?
- ¿Qué significa “correcto” (consultas, checksums, acciones UI, conteos de mensajes)?
-
Congela el inventario.
- Exporta archivos Compose y referencias de env/secretos.
- Lista volúmenes y bind mounts por contenedor.
- Registra referencias de imágenes (tags o digests).
-
Provisiona un objetivo de restauración limpio.
- Misma familia de OS, CPU/memoria similar, mismas elecciones de sistema de archivos.
- Misma ruta de red a backups y registros (o explícitamente diferente, si pruebas una región DR).
-
Obtén artefactos de backup y valida integridad.
- Checksum, descifrar, listar contenidos, verificar marcas de tiempo.
- Confirma que tienes claves y contraseñas en el modelo de acceso que esperas durante un incidente.
-
Restaura primero a staging.
- Bind mounts en
/restore-staging. - Volúmenes vía contenedores helper en volúmenes recién creados.
- Bind mounts en
-
Aplica permisos, etiquetas y propiedad.
- Los volúmenes de BD deben coincidir con las expectativas de UID/GID del contenedor.
- SELinux/AppArmor: asegura etiquetas y opciones de montaje correctas.
-
Arranca la pila fijando imágenes conocidas como buenas.
- Haz pull de imágenes; si falla el pull, usa imágenes cacheadas/offline.
- Arranca BD primero, luego app, luego proxies de borde.
-
Verifica la correctitud.
- Endpoint de salud + al menos una consulta de datos por servicio crítico.
- Para colas/caches: verifica si necesitas restaurarlos (a menudo no).
-
Mide tiempos y redacta el informe del simulacro.
- Inicio/fin de restauración, throughput de transferencia, cuellos de botella, fallos, correcciones.
- Actualiza el runbook y automatiza los pasos frágiles.
Qué automatizar después de tu primer simulacro honesto
- Exportación de inventario: montajes, volúmenes, imágenes, configuraciones Compose.
- Generación de manifiesto de backup: rutas y volúmenes esperados, tamaños, marcas de tiempo.
- Checks de integridad: checksums, pruebas de archivos, restauraciones periódicas a sandbox.
- Normalización de permisos: mapeo conocido de UID/GID por servicio.
- Retención de imágenes: conserva imágenes requeridas para la ventana RPO (o exporta tarballs).
Errores comunes: síntoma → causa raíz → solución
1) “Los contenedores están arriba, pero la app está vacía”
Síntoma: Las healthchecks pasan, pero faltan datos de usuario o se reiniciaron a valores por defecto.
Causa raíz: Restauraste en el nombre de volumen equivocado, o Compose creó un volumen nuevo y vacío por mismatch de nombre de proyecto.
Solución: Inspecciona montajes (docker inspect), asegura que los nombres de volumen coincidan y nombra volúmenes explícitamente en Compose en vez de depender del scope implícito del proyecto.
2) “Permiso denegado” en bind mounts restaurados
Síntoma: Contenedores fallan con errores de acceso a archivos; los archivos parecen correctos en el host.
Causa raíz: Etiquetas SELinux incorrectas, o un contenedor sin root espera otra propiedad que la restauración produjo.
Solución: Usa opciones de montaje :z/:Z
3) Postgres/MySQL arranca y luego se comporta raro bajo carga
Síntoma: La BD arranca, luego ves errores tipo corrupción o crashes.
Causa raíz: Backup crash-consistente tomado sin coordinar con la BD; estado WAL/checkpoint inconsistente.
Solución: Prefiere métodos de backup nativos de la BD para restauraciones duraderas; si usas snapshots, coordina con el modo de backup soportado por la BD y valida en sandbox.
4) La restauración es “lenta sin motivo”
Síntoma: Horas de restauración, CPU al máximo, discos infrautilizados.
Causa raíz: Descompresión/cifrado single-thread o nivel de compresión muy alto; millones de archivos pequeños amplifican operaciones de metadata.
Solución: Benchmark de descompresión, considera menor compresión o herramientas paralelas, y reestructura backups (p. ej., archivos por volumen) para reducir thrash de metadata.
5) No puedes hacer pull de imágenes durante la restauración
Síntoma: Auth al registro falla, DNS falla o las imágenes ya no existen.
Causa raíz: Credenciales guardadas solo en el host antiguo; retención del registro garbage-collectó tags en los que confiabas; dependencia de límites de rate de registros públicos.
Solución: Almacena credenciales de registro en un gestor de secretos recuperable, fija por digest o tags inmutables y guarda una caché/offline de imágenes críticas.
6) Compose “funciona en prod” pero falla en el host de restauración
Síntoma: Mismo archivo Compose, comportamiento distinto: puertos, DNS, redes, problemas de MTU.
Causa raíz: Configuración oculta del host en deriva: sysctls, iptables, módulos de kernel, daemon.json personalizado o redes específicas del cloud.
Solución: Codifica el aprovisionamiento del host (IaC), exporta y versiona configuraciones del daemon y haz un simulacro de host limpio al año.
7) El backup está presente, pero las claves no
Síntoma: Ves el archivo de backup pero no puedes descifrarlo o acceder durante la respuesta al incidente.
Causa raíz: Claves de cifrado/contraseñas bloqueadas por una persona, un portátil muerto o un SSO roto.
Solución: Practica la recuperación de claves durante los simulacros, guarda accesos de emergencia (break-glass) correctamente y verifica el procedimiento con un rol de on-call con privilegios mínimos necesarios.
8) Restauraste la configuración pero no las dependencias aburridas
Síntoma: La app arranca pero no puede enviar correo, no alcanza al proveedor de pagos o fallan callbacks.
Causa raíz: Faltan certificados TLS, reglas de firewall, registros DNS, secretos de webhook o listas de permitidos salientes.
Solución: Trata dependencias externas como parte del “estado de despliegue” y pruébalas en el simulacro (o simula explícitamente y documenta).
Preguntas frecuentes
1) ¿Debería respaldar /var/lib/docker?
Usualmente no. Haz backup de los volúmenes y de cualquier directorio bind-mounted de la aplicación, además de los configs Compose y referencias de secretos.
Respaldar todo el directorio raíz de Docker es frágil entre versiones, drivers de almacenamiento y diferencias de host.
2) ¿Cuál es la forma más segura de respaldar una base de datos en Docker?
Usa el mecanismo de backup soportado por la base de datos (dumps lógicos, base backups, archivado de WAL, etc.) y valida restaurando en un sandbox.
Los backups a nivel de sistema de archivos pueden funcionar si se coordinan correctamente, pero “pareció bien una vez” no es un método.
3) ¿Con qué frecuencia debo correr simulacros de restauración?
Trimestral para sistemas críticos es una línea base sensata. Mensual si el sistema cambia constantemente o si RTO/RPO son estrictos.
También ejecuta un simulacro tras cambios mayores: migración de almacenamiento, upgrade de Docker, upgrade de base de datos o cambio de herramientas de backup.
4) ¿Puedo ejecutar un simulacro sin duplicar datos de producción (preocupaciones de privacidad)?
Sí: usa datasets enmascarados, fixtures sintéticos o restaura en un entorno aislado cifrado con controles de acceso estrictos.
Pero aún necesitas restaurar la estructura realista: permisos, tamaños, conteos de archivos, esquema y comportamiento de runtime.
5) ¿Qué es lo que más hace explotar el tiempo de restauración?
Archivos pequeños y árboles con mucho metadata, especialmente cuando se combinan con cifrado y compresión. Puedes tener ancho de banda suficiente
y aun así estar bloqueado por CPU o IOPS.
6) ¿Debería comprimir los backups?
Usualmente sí, pero elige una compresión que coincida con tus restricciones de restauración. Si estás limitado por CPU durante la restauración, una compresión fuerte
perjudica el RTO. Mídelo con una extracción cronometrada durante los simulacros y ajusta.
7) ¿Cómo sé si restauré lo correcto?
No confíes en el estado de los contenedores. Usa checks a nivel de aplicación: ejecuta consultas de BD, verifica conteos de registros, valida un cliente/cuenta conocido
o ejecuta una transacción de negocio de solo lectura. Automatiza estos checks en el simulacro.
8) ¿Necesito restaurar Redis u otras caches?
Típicamente no—las caches son reconstruibles y restaurarlas puede reintroducir estado malo. Pero debes confirmar que la app tolera una cache vacía
y que la configuración de cache (contraseñas, TLS, políticas de maxmemory) está respaldada.
9) ¿Qué pasa con los secretos en variables de entorno?
Si tu producción depende de un archivo env, ese archivo forma parte del estado de despliegue y debe ser recuperable. Mejor: migra secretos a un manager
de secretos o al equivalente de Docker secrets, e incluye la recuperación break-glass en el simulacro.
10) ¿Puedo hacer esto con Docker Compose y aun así ser “enterprise-grade”?
Sí, si tratas Compose como un artefacto con versionado, imágenes fijadas, restauraciones probadas y gestión disciplinada del estado.
“Enterprise-grade” es un comportamiento, no una elección de herramienta.
Conclusión: pasos siguientes que puedes hacer esta semana
Si solo haces una cosa, programa un simulacro de restauración en un host limpio y mídelo. No en producción, no en tu laptop, no “alguna vez”.
Ponlo en el calendario e invita a quien sea responsable de backups, almacenamiento y la app. Quieres tener todos los modos de falla en la sala.
Luego haz estos pasos, en orden:
- Inventaria montajes para cada contenedor con estado y anota las rutas autorizadas y nombres de volúmenes.
- Separa artefactos en datos (volúmenes), bind mounts y configuración de despliegue para poder restaurar de forma quirúrgica.
- Valida integridad del set de backups más reciente y demuestra que tienes las claves para descifrarlo con permisos de on-call.
- Restaura en un sandbox y ejecuta checks de correctitud a nivel de app, no solo “el contenedor está en ejecución”.
- Mide RTO, identifica el paso más lento y arregla esa única cosa antes de optimizar otra cosa.
Las copias de seguridad que nunca restauraste no son backups. Son optimismo comprimido. Ejecuta el simulacro, anota qué falló y hazlo aburrido.