Rollback de Docker Compose: la ruta más rápida para recuperarse de un despliegue fallido

¿Te fue útil?

Enviaste un cambio en Compose, los healthchecks empezaron a alertar y ahora tu cerebro de guardia hace
esa cosa que no recuerda si docker compose down borra volúmenes por defecto (no lo hace—salvo que se lo indiques).
Mientras tanto, el negocio quiere un cronograma, el panel está en rojo y tus notas de despliegue dicen “pequeño refactor”.

Esta es la guía de rollback que necesitas cuando estás cansado, impaciente y tratando de dejar producción estable
antes de que Slack decida que por defecto eres el nuevo responsable del incidente.

El principio: rollback es un cambio, no una oración

Docker Compose es una herramienta de “estado deseado” para un único host (o un pequeño conjunto de hosts si eres disciplinado).
No es un sistema de orquestación completo con rollbacks integrados, conjuntos de réplicas o historial de despliegues.
Compose reemplazará contenedores, conservará volúmenes, reutilizará redes y seguirá con su vida. Eso es estupendo—hasta que necesitas retroceder rápido.

Hacer rollback con Compose se reduce a tres cosas:

  1. Contenedores: volver a ejecutar la imagen anterior que se sabía que funcionaba.
  2. Configuración: restaurar el archivo Compose anterior (y cualquier archivo env) que produjo ese estado conocido bueno.
  3. Estado: decidir qué hacer con los volúmenes y los cambios de esquema/datos.

Si solo manejas los contenedores, “harás rollback” y seguirás roto porque las migraciones de BD ya se ejecutaron,
o porque cambió la configuración del proxy inverso, o porque la app lee una nueva variable de entorno que ya no existe.

La verdad operacional es esta: el rollback más rápido suele ser el que evita tocar el estado.
Si tu despliegue incluyó una migración de BD que no es compatible hacia atrás, volver a la imagen anterior puede no restaurar la funcionalidad.
Entonces tu “rollback” es parcial. Esos son los que arruinan las tardes.

Una cita útil para tener en la cabeza durante incidentes viene de John Allspaw (idea parafraseada): La fiabilidad trata sobre cómo se comportan los sistemas bajo estrés, no sobre cómo se ven en los diagramas.
Los rollbacks son pruebas de estrés que no programaste.

Datos contextuales interesantes (por qué los rollbacks en Compose son raros)

  • Compose empezó como “Fig” (2013): se diseñó para hacer entornos de desarrollo multi-contenedor manejables, no para ser el historial de despliegues en producción.
  • “docker-compose” (Python) vs “docker compose” (plugin CLI): el plugin moderno se integra mejor con los contextos de Docker y es donde primero llegan las nuevas funciones.
  • Compose no tiene “deshacer” integrado: a diferencia de algunos orquestadores, no existe un controlador de rollback nativo. Tu “historial” es tu repo Git y tu registro de imágenes.
  • Las etiquetas de imagen no son inmutables a menos que las fuerces: etiquetas como latest pueden moverse, sobrescribirse o re-publicarse. Los digests no mienten.
  • Los IDs de contenedor son desechables; los volúmenes no: el comportamiento por defecto de Compose mantiene los volúmenes con nombre, lo cual es bueno para persistencia y aterrador para sorpresas en rollbacks.
  • Compose usa el nombre del proyecto para el scope: el nombre del proyecto controla nombres de contenedores/redes/volúmenes. Un cambio de nombre de proyecto puede “huérfano” la pila anterior.
  • Los healthchecks son recientes respecto a “esto corre en mi laptop”: muchas pilas siguen “arrancando” incluso si están funcionalmente muertas. Los healthchecks aceleran rollbacks porque te dan una señal de stop/go.
  • El comportamiento de pull del registro depende de lo que haya local: si la imagen antigua existe en disco, el rollback puede ser instantáneo; si necesitas tirar desde un registro lento, acabas de encontrar tu cuello de botella.

Conclusión: Compose puede ejecutar sistemas en producción, pero no te sostiene de la mano en la seguridad del rollback.
Tienes que construir esa musculatura.

Las rutas de rollback más rápidas (elige una)

Ruta A: revertir al commit anterior en Git y redeplegar

Esto es lo más limpio cuando la falla es causada por la configuración de Compose, variables de entorno, entrypoints, límites de recursos,
o una etiqueta de imagen mala introducida en el archivo Compose.

También es lo mejor para auditoría: tu rollback es un commit, no una serie de comandos de terminal en pánico.
Si despliegas desde un repo en el host, revertir suele ser un git checkout y un docker compose up -d.

Ruta B: fijar a un digest conocido y redeplegar

Si el archivo Compose “está bien” pero la etiqueta de la imagen apunta a una build mala, no pierdas tiempo negociando con etiquetas.
Fija el digest exacto de la última imagen conocida buena. Los digests son lo más cercano a la verdad que tendrás a las 3 AM.

Ruta C: detener la hemorragia—ejecutar manualmente la imagen anterior

Este es el camino de “romper el cristal de emergencia”. Es más rápido que arreglar el archivo Compose cuando tu herramienta de despliegue está rota,
o cuando necesitas servicio de vuelta ahora y lo limpiarás después.

Cambias elegancia por velocidad. También creas deriva, así que toma notas y programa la limpieza. Tu futuro yo no es tu empleado.

Ruta D: restaurar datos/estado (solo si es imprescindible)

Esto es para cuando el despliegue malo mutó el estado de forma intolerable:
migraciones destructivas, jobs en background incorrectos, o un consumidor de colas que “útilmente” procesó mensajes hacia el vacío.

Restaurar estado es más lento, más arriesgado y requiere coordinación. Si puedes evitarlo, evítalo.

Broma #1: Los planes de rollback son como los extintores—a todos les gusta tenerlos, y nadie quiere descubrir que son decorativos.

Playbook de diagnóstico rápido (encuentra el cuello de botella en minutos)

El objetivo no es convertirte en un científico forense. El objetivo es responder: “¿Es el rollback la medida correcta, y qué lo bloquea?”
Trabaja de arriba hacia abajo, y para cuando tengas un plan confiable.

Primero: ¿la falla está en la app, el contenedor o el host?

  • Revisa la salud del contenedor y los bucles de reinicio: si los contenedores se reinician, estás en crash-loop. El rollback suele ser correcto.
  • Revisa logs por errores inmediatos: variables de entorno faltantes, migraciones fallando, conexión rechazada, errores de sintaxis en configuración.
  • Revisa recursos del host: disco lleno, kills por OOM, inodos agotados. El rollback no arreglará un disco lleno.

Segundo: ¿puedes recuperar los bits antiguos rápidamente?

  • ¿La imagen anterior está todavía local? Si sí, el rollback son minutos. Si no, necesitas pulls y acceso al registro.
  • ¿Conoces el último digest bueno? Si sí, fíjalo. Si no, estás por hacer arqueología en logs de CI o metadatos del registro.
  • ¿El despliegue tocó estado? Si sí, evalúa compatibilidad hacia atrás y si necesitas rollback de datos o arreglos hacia adelante.

Tercero: decide tu estrategia de rollback

  • Regresión de configuración: revertir commit de Compose.
  • Build de imagen mala: fijar digest.
  • Problema del host/recursos: arreglar el host primero, luego decidir si el rollback sigue siendo necesario.
  • Mutación de estado: coordinar: solo rollback de la app puede no bastar.

Trátalo como triaje, no como club de debate. Si pasas 20 minutos discutiendo si “realmente fue el despliegue”, ya elegiste tiempo de inactividad.

Tareas prácticas con comandos, salidas y decisiones

Estos son los comandos que realmente uso durante incidentes. Cada tarea incluye (a) el comando, (b) qué significa la salida,
y (c) la decisión que tomas.

Task 1: Confirmar qué piensa Compose que es el proyecto

cr0x@server:~$ docker compose ls
NAME            STATUS              CONFIG FILES
payments        running(6)          /srv/payments/compose.yaml

Qué significa: Tienes un proyecto Compose llamado payments y Docker lo ve como en ejecución.
Si el nombre de tu proyecto cambió recientemente, podrías estar mirando la pila equivocada.

Decisión: Si el proyecto esperado no aparece listado, estás en territorio de “directorio incorrecto / contexto incorrecto / nombre de proyecto incorrecto”. Arregla eso antes de tocar nada.

Task 2: Identificar qué servicios se están reiniciando o están unhealthy

cr0x@server:~$ docker compose -p payments ps
NAME                 IMAGE                              COMMAND                  SERVICE   STATUS                    PORTS
payments-api-1       registry.local/payments:1.9.2      "/entrypoint.sh"         api       Restarting (1) 8s ago
payments-web-1       registry.local/payments-web:1.9.2  "nginx -g 'daemon off;'" web       Up 3 minutes (healthy)     0.0.0.0:443->443/tcp
payments-db-1        postgres:15.6                      "docker-entrypoint.s…"   db        Up 2 days (healthy)        5432/tcp

Qué significa: El API está en crash-loop. Web y DB parecen saludables. Esto es candidato para rollback de la imagen de la app.

Decisión: Si solo un servicio falla, revierte ese servicio primero (o fija su imagen) en lugar de derribar toda la pila.

Task 3: Tomar las últimas 200 líneas de logs del servicio fallido

cr0x@server:~$ docker compose -p payments logs --no-color --tail=200 api
api-1  | ERROR: missing required env var PAYMENTS_SIGNING_KEY
api-1  | FATAL: cannot start

Qué significa: Esto es una regresión de configuración/entorno, no un bug de código. Alguien agregó una variable de entorno requerida.

Decisión: Revertir la configuración de Compose (archivo env o compose.yaml), o restaurar el secreto faltante. Hacer rollback de la imagen podría no ayudar si la imagen también espera la variable.

Task 4: Verificar qué configuración está usando Compose (renderizada)

cr0x@server:~$ docker compose -p payments config
services:
  api:
    environment:
      PAYMENTS_SIGNING_KEY: ""
    image: registry.local/payments:1.9.2
    restart: always

Qué significa: Compose resolvió la variable de entorno como una cadena vacía. Por eso la app se niega a iniciar.

Decisión: Arreglar la inyección de env (env_file, variable exportada, secrets) o revertir el cambio. No “hotfixees” editando contenedores; no sobrevivirá a un reinicio.

Task 5: Comprobar si la imagen anterior sigue local (potencial rollback rápido)

cr0x@server:~$ docker images --digests | grep registry.local/payments | head
registry.local/payments   1.9.2   sha256:aa11...   3d2f1c9d0b2a   45 minutes ago   312MB
registry.local/payments   1.9.1   sha256:bb22...   9a8e7d6c5b4f   2 days ago       311MB

Qué significa: La imagen antigua (1.9.1) ya está en disco. Ese es tu camino dorado: rollback sin esperar pulls.

Decisión: Prefiere volver a 1.9.1 (o a su digest) inmediatamente, y luego realiza una depuración más profunda cuando esté estable.

Task 6: Fijar el servicio a un tag conocido bueno y redeplegar solo ese servicio

cr0x@server:~$ sed -i 's|registry.local/payments:1.9.2|registry.local/payments:1.9.1|g' /srv/payments/compose.yaml
cr0x@server:~$ docker compose -p payments up -d --no-deps api
[+] Running 1/1
 ✔ Container payments-api-1  Started

Qué significa: Solo el contenedor del API fue recreado. Las dependencias quedaron intactas.

Decisión: Usa --no-deps durante el rollback cuando las dependencias estén sanas. No renueves contenedores funcionales durante un incidente a menos que disfrutes multiplicar variables.

Task 7: Confirmar que el rollback “tomó” (imagen y salud)

cr0x@server:~$ docker compose -p payments ps api
NAME             IMAGE                            COMMAND            SERVICE   STATUS              PORTS
payments-api-1   registry.local/payments:1.9.1    "/entrypoint.sh"   api       Up 20 seconds

Qué significa: El contenedor está ejecutándose con 1.9.1. Si tienes healthchecks, espera a que aparezca healthy.

Decisión: Si se mantiene en ejecución y atiende tráfico, ganaste tiempo. Si sigue fallando, el problema puede ser configuración/estado, no código.

Task 8: Fijar por digest (más fuerte que las etiquetas)

cr0x@server:~$ docker inspect --format '{{index .RepoDigests 0}}' registry.local/payments:1.9.1
registry.local/payments@sha256:bb22cc33dd44ee55ff66...

Qué significa: Tienes el digest inmutable para la build conocida buena.

Decisión: Actualiza Compose para usar el digest cuando necesites certeza, especialmente si las etiquetas pueden re-publicarse.

Task 9: Cambiar Compose a digest y redeplegar (previene drift de etiquetas)

cr0x@server:~$ sed -i 's|registry.local/payments:1.9.1|registry.local/payments@sha256:bb22cc33dd44ee55ff66...|g' /srv/payments/compose.yaml
cr0x@server:~$ docker compose -p payments up -d --no-deps api
[+] Running 1/1
 ✔ Container payments-api-1  Recreated

Qué significa: Redeplegaste el servicio referenciando una imagen por contenido.

Decisión: Para estabilizar tras el incidente, fija digests para servicios críticos. Puedes volver a tags semánticos cuando tengas mejores controles.

Task 10: Detectar si tu rollback está bloqueado por presión de disco en el host

cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  118G  117G  0G   100% /

Qué significa: Tu filesystem root está lleno. Tirar imágenes, escribir logs y arrancar contenedores puede fallar de formas caóticas.

Decisión: Libera espacio en disco antes de cualquier otra cosa. Los rollbacks también necesitan disco—especialmente si requieren tirar una imagen antigua.

Task 11: Limpieza rápida sin borrar volúmenes (relativamente segura)

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        18        28.6GB    11.2GB (39%)
Containers      35        9         1.3GB     1.1GB (84%)
Local Volumes   14        10        96.4GB    0B (0%)
Build Cache     0         0         0B        0B
cr0x@server:~$ docker container prune -f
Deleted Containers:
c8a1...
Total reclaimed space: 1.1GB

Qué significa: Los contenedores estaban ocupando espacio; los volúmenes son la porción grande pero no son recuperables con este comando.

Decisión: Prunea contenedores primero. Evita prunear volúmenes durante un rollback a menos que tengas backups verificados y te guste el juego de alto riesgo.

Task 12: Identificar si creaste accidentalmente servicios huérfanos

cr0x@server:~$ docker compose -p payments up -d
[+] Running 6/6
 ✔ Container payments-web-1  Running
 ✔ Container payments-api-1  Running
 ✔ Container payments-db-1   Running
 ! Found orphan containers ([payments-worker-1]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.

Qué significa: Algo fue renombrado/eliminado y un contenedor viejo sigue ahí.

Decisión: Durante el rollback, no elimines huérfanos a ciegas. Ese huérfano podría seguir haciendo trabajo importante (como drenar una cola). Evalúa y luego limpia deliberadamente.

Task 13: Confirmar la razón de salida del contenedor (OOM vs falla de app)

cr0x@server:~$ docker inspect -f '{{.State.Status}} {{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' payments-api-1
exited 137 OOMKilled=true

Qué significa: Código de salida 137 con OOMKilled indica que el kernel mató tu contenedor. Eso no es una regresión de código hasta que se demuestre lo contrario.

Decisión: El rollback puede “funcionar” si la versión antigua usa menos memoria, pero la solución real son límites de memoria, capacidad del host o una fuga. Atiende la memoria ahora.

Task 14: Revisar presión de memoria del host y logs OOM recientes

cr0x@server:~$ free -m
               total        used        free      shared  buff/cache   available
Mem:           15984       15410         120         210         454         260
Swap:           2047        2047           0
cr0x@server:~$ journalctl -k --since "30 min ago" | tail -n 5
kernel: Out of memory: Killed process 28411 (payments-api) total-vm:2456120kB, anon-rss:1220040kB, file-rss:0kB, shmem-rss:0kB

Qué significa: El host se quedó sin memoria y el swap está agotado. Cualquier versión podría morir; el rollback por sí solo es un parche.

Decisión: Reduce carga, escala hacia abajo otros servicios, aumenta la memoria o establece límites sensatos. Luego redeploya. De lo contrario vas a “hacer rollback” hacia el mismo OOM.

Task 15: Ver qué cambió entre dos revisiones de Compose (diff de configuración)

cr0x@server:~$ cd /srv/payments
cr0x@server:~$ git log --oneline -5
a12b9f3 bump api to 1.9.2 and add signing key
6c0dd21 pin postgres and tune healthcheck
b19e8aa add worker concurrency env var
cr0x@server:~$ git show a12b9f3 -- compose.yaml | sed -n '1,120p'
diff --git a/compose.yaml b/compose.yaml
index 31e2c0a..8f19c11 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -12,6 +12,7 @@ services:
   api:
     image: registry.local/payments:1.9.2
     environment:
+      - PAYMENTS_SIGNING_KEY=${PAYMENTS_SIGNING_KEY}

Qué significa: La nueva versión agregó un secreto requerido. Tu inyección de env no lo siguió.

Decisión: Revertir el commit o suministrar el secreto correctamente. Si el secreto debe existir en adelante, arregla la tubería de secretos y despliega hacia adelante en lugar de hacer rollback.

Task 16: Revertir por Git a la última configuración conocida buena

cr0x@server:~$ cd /srv/payments
cr0x@server:~$ git checkout 6c0dd21 -- compose.yaml .env
cr0x@server:~$ docker compose -p payments up -d
[+] Running 3/3
 ✔ Container payments-api-1  Recreated
 ✔ Container payments-web-1  Running
 ✔ Container payments-db-1   Running

Qué significa: Revertiste la configuración y la aplicaste. El contenedor del API se recreó en consecuencia.

Decisión: Usa Git como tu libro mayor de rollbacks. Si no puedes reproducir el “último conocido bueno” desde control de versiones, no tienes un plan de rollback—solo nostalgia.

Task 17: Validar rápidamente la alcanzabilidad externa (chequeo a nivel servicio)

cr0x@server:~$ curl -fsS -o /dev/null -w "%{http_code}\n" https://localhost/health
200

Qué significa: Tu endpoint local devuelve 200. Esto no es una prueba de integración completa, pero es una señal rápida.

Decisión: Si la salud es buena localmente pero los usuarios aún ven fallos, el problema puede ser DNS, balanceador, firewall o dependencias ascendentes.

Task 18: Hacer snapshot de un volumen nombrado antes de tocarlo (seguro y barato)

cr0x@server:~$ docker volume ls | grep payments
local     payments_dbdata
cr0x@server:~$ mkdir -p /var/backups/payments
cr0x@server:~$ docker run --rm -v payments_dbdata:/data -v /var/backups/payments:/backup alpine sh -c "cd /data && tar -czf /backup/payments_dbdata_$(date +%F_%H%M).tgz ."

Qué significa: Creaste un tarball con el contenido del volumen. No es perfecto para bases de datos en vivo, pero es mejor que “no lo hicimos”.

Decisión: Para bases de datos reales, prefiere herramientas de backup nativas. Pero cuando vas a hacer algo arriesgado, toma un snapshot o un backup primero.

Broma #2: “Simplemente haz rollback” es una frase encantadora hasta que descubres que tu base de datos no recibió el memo.

Tres mini-historias del mundo corporativo

1) El incidente causado por una suposición equivocada

Una empresa mediana ejecutaba una API de pagos en un solo host potente usando Docker Compose.
El proceso de despliegue era “tirar nuevas imágenes, ejecutar docker compose up -d.”
Tenían monitoreo, alertas y un on-call rotatorio. También tenían una suposición: las etiquetas eran lo bastante inmutables.

Un desarrollador subió payments-api:1.8.4 al registro, vio que un flag de build estaba mal, y re-subió la misma etiqueta con una imagen corregida.
Sin mala intención; solo costumbre de entornos de desarrollo. El registro lo permitió. Nadie lo notó.
Dos días después, ocurrió un incidente tras un reinicio rutinario del host. El nodo volvió, Compose arrancó contenedores,
y tiró 1.8.4—pero no la imagen que todos habían estado ejecutando. Misma etiqueta, bits distintos.

El on-call hizo lo que decía el runbook: revertir a 1.8.4. El rollback “funcionó” en el sentido de que no cambió nada.
Perdieron una hora persiguiendo diferencias de configuración fantasma y sospechando actualizaciones de kernel.
La causa real fue más simple: su objetivo de rollback no era un objetivo. Era una etiqueta móvil.

La solución no fue glamorosa. Empezaron a fijar digests para despliegues en producción y mantuvieron etiquetas amigables para humanos,
pero no como fuente de verdad. También restringieron el registro para que sobrescribir una etiqueta existente requiriera acción administrativa.
Nadie ama esto en el momento. A todos les encanta la primera vez que les salva.

2) La optimización que salió mal

Un equipo de plataforma de datos quiso despliegues más rápidos. Su pila Compose tenía media docena de servicios y una red compartida.
Tirar imágenes durante el despliegue era lento, así que optimizaron: mantuvieron imágenes localmente agresivamente y evitaron tirar salvo que fuera necesario.
También pusieron políticas de restart en always en todas partes, porque disponibilidad, ¿no?

Una release mala entró con una fuga de memoria sutil en un servicio worker. Al principio parecía bien.
En unas horas la memoria subió, el kernel comenzó a recuperar memoria agresivamente y finalmente mató contenedores por OOM.
Con restart=always, los workers se reiniciaron al instante, recargaron, volvieron a filtrar y se descontrolaron.
Mientras tanto el host quedó tan escaso de memoria que los servicios “buenos” comenzaron a fallar también.

El on-call intentó rollback. Pero el host estaba tan presionado que incluso arrancar la imagen antigua era inestable.
Los logs se truncaban. Hacer exec en contenedores tardaba. “Rollback” se convirtió en una carrera entre el kernel y el operador.
La misma política de reinicio que ayudó en fallos transitorios convirtió un incidente controlado en uno ruidoso.

La mejora eventual: establecieron límites de memoria y reservaron margen, ajustaron políticas de restart por servicio,
y añadieron una comprobación canaria en despliegue para crecimiento de memoria bajo carga. La optimización—evitar pulls—no fue la verdadera villana.
Faltaban los guardarraíles.

3) La práctica aburrida pero correcta que salvó el día

Un equipo de herramientas internas ejecutaba Compose en un par de hosts, cada uno con un directorio “project”.
Su práctica era dolorosamente aburrida: cada despliegue era un commit en Git, cada commit registraba digests de imagen en el archivo Compose,
y cada release tenía una nota de “último conocido bueno” en el repo. También hacían backups nocturnos de volúmenes y probaban restauraciones mensualmente.
Sí, de verdad.

Una release introdujo una migración de esquema que debía ser aditiva.
No lo fue. Renombró una columna usada por una ruta de código antiguo y rompió un job de reporting.
La API de producción seguía mayormente bien, pero el job llenó logs de errores y generó carga.

La respuesta al incidente fue casi aburrida. Revirtieron al commit anterior solo para el servicio del job,
redeplegaron con --no-deps, y confirmaron que el sistema de reporting se recuperó.
Luego programaron una migración de corrección hacia adelante con compatibilidad hacia atrás adecuada.
Sin heroísmos, sin war room que se convierta en seminario de filosofía.

El detalle clave: como fijaban digests, no tuvieron que adivinar qué significaba “versión anterior”.
Como practicaban restauraciones, sabían qué pasaría si necesitaban rollback de datos.
No terminaron restaurando datos. No fue necesario. Pero la opción existía, y eso cambia la calma con la que la gente actúa.

Errores comunes: síntoma → causa raíz → arreglo

1) “Rollback redeployó, pero nada cambió”

Síntoma: Cambias la etiqueta, ejecutas docker compose up -d, y el servicio sigue roto.

Causa raíz: La etiqueta “antigua” apunta a bits nuevos (etiqueta sobrescrita), o Compose no recreó el contenedor porque no detectó un cambio.

Arreglo: Fija por digest y fuerza la recreación si es necesario.

2) “El contenedor arranca, luego sale inmediatamente”

Síntoma: Crash-loop; los logs muestran variables de entorno faltantes o errores de parseo de configuración.

Causa raíz: Deriva de configuración, falta de inyección de secretos, o una variable de entorno requerida ahora por la app.

Arreglo: Usa docker compose config para ver valores resueltos. Restaura el commit/env anterior o suministra el secreto faltante correctamente.

3) “Rollback lo empeoró; ahora varios servicios están caídos”

Síntoma: Revertiste un cambio y de repente DB, proxy y workers se reinician.

Causa raíz: Ejecutaste un docker compose down completo (o eliminaste redes) y recreaste todo, causando churn de dependencias o IPs cambiadas en una pila frágil.

Arreglo: Prefiere docker compose up -d --no-deps <service> y evita derribar redes a mitad de un incidente.

4) “La versión antigua no puede hablar con la base de datos”

Síntoma: Tras el rollback, la app muestra errores de columnas/tablas faltantes o esquema incompatible.

Causa raíz: Se aplicaron migraciones no compatibles hacia atrás por el despliegue malo.

Arreglo: O despliegas hacia adelante con una versión de app que soporte el nuevo esquema, o realizas una restauración de datos coordinada (backup/restore) si es factible.

5) “El rollback es lento porque los pulls tardan una eternidad”

Síntoma: docker compose up se queda estancado tirando imágenes; el tiempo de recuperación se estira.

Causa raíz: Las imágenes antiguas no están en caché local, el ancho de banda del registro es limitado, o hay problemas de DNS/red.

Arreglo: Mantén las últimas imágenes buenas localmente (o pre-pull durante el despliegue), verifica la accesibilidad del registro y considera un mirror/cache interno.

6) “Hicimos rollback, pero los usuarios siguen viendo errores”

Síntoma: Los healthchecks pasan, pero el tráfico real falla de forma intermitente.

Causa raíz: Cambiaron dependencias externas (config LB, certificado TLS, DNS), o un rollback parcial dejó un sistema con versiones mixtas.

Arreglo: Confirma comportamiento end-to-end vía curl desde afuera, verifica rutas del proxy inverso y asegúrate de que todos los servicios interdependientes estén en versiones compatibles.

7) “Todo se está reiniciando; los logs están vacíos”

Síntoma: Los contenedores se reinician rápidamente y no puedes capturar logs.

Causa raíz: OOM kills, disco lleno, o crash antes de que stdout se vacíe.

Arreglo: Revisa docker inspect para OOMKilled, revisa journalctl -k, y arregla la presión del host primero.

8) “El rollback borró datos”

Síntoma: La BD arranca vacía después del “rollback”.

Causa raíz: Alguien ejecutó docker compose down -v o eliminó volúmenes con nombre, o cambió nombres de volúmenes al cambiar el nombre del proyecto.

Arreglo: Detente. Identifica volúmenes con docker volume ls. Restaura desde backups. Prevénlo bloqueando runbooks y usando nombres explícitos de volúmenes.

Listas de verificación / plan paso a paso

Checklist: rollback de 10 minutos (cuando el estado es compatible)

  1. Congelar despliegues: evita que CI/CD empuje más cambios mientras haces triaje.
  2. Identificar servicios fallidos: docker compose ps. Elige el menor radio de impacto primero.
  3. Leer logs: docker compose logs --tail=200 <service>. Decide: configuración vs código vs host.
  4. Confirmar salud del host: disco (df -h), memoria (free -m), logs del kernel OOM.
  5. Encontrar la última imagen buena: caché local (docker images), registro de CI o metadatos del registro.
  6. Hacer rollback de un servicio: cambiar tag/digest, luego docker compose up -d --no-deps <service>.
  7. Verificar salud: docker compose ps, luego una petición real (curl).
  8. Anunciar estado: “Servicio estable en versión X; investigación de causa raíz pendiente.”
  9. Capturar evidencias: logs, digests, IDs de commit. Tu yo futuro necesita recibos.
  10. Planear arreglo hacia adelante: no vivas permanentemente en rollback; programa la reparación adecuada.

Checklist: rollback cuando las migraciones pudieron cambiar estado

  1. Determinar qué se ejecutó: revisa logs de la app por mensajes de ejecución de migraciones; revisa la tabla de migraciones en la BD si aplica.
  2. Evaluar compatibilidad: ¿la app antigua puede correr contra el nuevo esquema? Si no, el rollback de la app no restaurará el servicio.
  3. Elegir estrategia:
    • Preferir arreglo hacia adelante si puedes enviar una app compatible rápidamente.
    • Restaurar datos si el despliegue corrompió o borró datos, o si el arreglo hacia adelante es demasiado lento.
  4. Proteger el estado actual: snapshot/backups de volúmenes antes de tocarlos.
  5. Coordinar tiempo de inactividad: restaurar app+BD no es una actividad solitaria en una empresa ocupada.
  6. Validar la restauración: versión de esquema, chequeos de conteo de filas y pruebas funcionales de la app.

Checklist: prevenir el “teatro de rollback” en Compose (parece rollback, pero no lo es)

  1. Deja de usar etiquetas flotantes (latest) en archivos Compose de producción.
  2. Registrar y desplegar por digest para servicios críticos.
  3. Mantener “último conocido bueno” en Git (compose + env + cualquier config montada en contenedores).
  4. Asegurarse de que los healthchecks existan y representen la readiness real, no solo “el proceso existe”.
  5. Practicar un rollback trimestral en un host de staging que se parezca a producción.
  6. Hacer backups de volúmenes con un método apropiado para los datos (preferir nativo de BD).

Preguntas frecuentes

1) ¿Docker Compose tiene un comando de rollback incorporado?

No. Compose aplica lo que declares. Tu rollback es “declarar el estado anterior” y aplicarlo de nuevo,
típicamente vía revert en Git y/o fijando una imagen más antigua por digest.

2) ¿Cuál es el rollback seguro más rápido si solo un servicio está roto?

Haz rollback solo de ese servicio: actualiza su referencia de imagen y ejecuta docker compose up -d --no-deps <service>.
No toques dependencias saludables.

3) ¿Debo usar docker compose down durante un rollback?

Usualmente no. down elimina contenedores y redes, y puede aumentar el radio de impacto.
Úsalo cuando necesites un estado limpio y entiendas las consecuencias (especialmente alrededor de redes y downtime).

4) ¿El rollback borrará mi base de datos?

No por defecto. Los volúmenes con nombre persisten entre up/down. La pérdida de datos ocurre normalmente cuando alguien ejecuta
docker compose down -v o cambia nombres de volumen/proyecto y “pierde” el volumen antiguo.

5) Etiquetas vs digests: ¿qué debería usar en producción?

Para rollbacks en producción, los digests son superiores porque son identificadores inmutables del contenido de la imagen.
Las etiquetas están bien para humanos, pero no las trates como evidencia.

6) ¿Cómo encuentro el último digest conocido bueno?

Lo ideal: tu pipeline de despliegue lo registra. Lo siguiente mejor: está ya en tu archivo Compose de la última despliegue.
Si no, inspecciona imágenes en caché local o consulta metadatos del registro (internamente) y correlaciónalo con notas de release.

7) ¿Qué pasa si el despliegue malo ejecutó migraciones que no son compatibles hacia atrás?

Volver la app puede no funcionar. O envías un arreglo hacia adelante que soporte el nuevo esquema,
o coordinas una restauración de datos. Por eso las migraciones “expand/contract” y la compatibilidad hacia atrás no son opcionales en producción.

8) ¿Por qué a veces Compose no recrea un contenedor después de que cambio cosas?

Si Compose no detecta un cambio significativo (o editaste el archivo equivocado/ruta equivocada), puede mantener el contenedor existente.
Confirma con docker compose config y docker compose ps. Si es necesario, fuerza la recreación de ese servicio.

9) ¿Cuál es la forma más segura de probar un rollback sin afectar el tráfico de producción?

Ejecuta la imagen antigua como servicio paralelo en otro puerto o nombre de proyecto, valídala contra un conjunto de dependencias de staging,
luego cambia el tráfico en el proxy. Compose no te da shifting de tráfico; tienes que construirlo (a menudo en Nginx/Traefik/HAProxy).

10) ¿Cuántas imágenes antiguas debería mantener localmente para acelerar el rollback?

Mantén al menos el último conocido bueno más una release anterior para cada servicio crítico, asumiendo que el disco lo permite.
El número correcto depende del tamaño de las imágenes y del presupuesto de disco, pero “cero” es el número equivocado.

Próximos pasos que puedes hacer hoy

El rollback en Compose no tiene por qué ser dramático. Tiene que ser determinista. Si te llevas una cosa de esto:
deja de confiar en etiquetas en emergencias, y deja de tratar los cambios de estado como un detalle posterior.

  1. Anota “último conocido bueno” como digest y commit Git para cada despliegue.
  2. Añade healthchecks que reflejen readiness, no solo “el proceso existe”.
  3. Practica un rollback de un servicio con --no-deps en un host no productivo.
  4. Decide tu política de estado: qué servicios pueden ejecutar migraciones destructivas y cómo las deshaces.
  5. Presupuesta espacio en disco y memoria para que el rollback no se vea bloqueado por un host en llamas.

El rollback más rápido en Compose es el que puedes ejecutar con los ojos medio cerrados: fija digest, redeploya el servicio roto,
verifica salud y sigue adelante. El drama es opcional. La disciplina no lo es.

← Anterior
Ceph OSD en Proxmox caído: disco vs red vs servicio — Identificación rápida
Siguiente →
¿Tendrán los PCs de consumo diseños unificados GPU+NPU?

Deja un comentario