Lifecycle de contenedores Docker | Nicolás Garzón
Texto
Copiar image
→ create
→ created
→ start
→ running
→ stop / exit / kill
→ exited
→ start o removeLa vida del contenedor está ligada a su proceso principal . Mientras ese proceso permanece activo, el contenedor se considera running. Cuando termina, Docker conserva metadata, writable layer y exit code hasta que el contenedor sea eliminado.
Comprender este lifecycle permite distinguir cuatro decisiones diferentes:
crear una instancia;
iniciar o detener su proceso;
reiniciar la misma instancia;
reemplazarla por una nueva.
Estas operaciones no son equivalentes y tienen consecuencias diferentes sobre configuración, filesystem, diagnóstico y disponibilidad.
Una aplicación necesita algo más que ejecutar un binario. El runtime debe conservar información sobre:
qué imagen se utilizó;
qué command y environment recibió;
qué mounts y networks tiene;
qué límites y permisos se aplicaron;
cuándo inició y terminó;
qué exit code produjo;
si fue terminada por OOM;
qué política de reinicio posee.
Si esa información desapareciera apenas termina el proceso, sería difícil diagnosticar fallos o reiniciar la instancia con la misma configuración. Docker separa el objeto administrativo del proceso vivo.
Texto
Copiar container object
├── configuración persistente mientras exista el objeto
├── writable layer
├── relaciones con networks y mounts
└── proceso principal, presente solo cuando está runningBash
Copiar docker container create \
--name api \
--env NODE_ENV = production \
--memory 512m \
my-api:1.0create prepara la configuración y recursos necesarios, pero todavía no inicia el proceso principal. El estado queda como created.
Esto permite inspeccionar la instancia antes de arrancarla:
Bash
Copiar docker inspect api
docker start apidocker start ejecuta el proceso configurado para un contenedor ya creado o detenido. No crea una writable layer nueva ni vuelve a resolver todos los flags desde cero.
Bash
Copiar docker run my-api:1.0run es una operación de conveniencia que combina, de forma conceptual:
Texto
Copiar pull si hace falta
→ create
→ start
→ attach según opcionesEntender esta composición ayuda a interpretar errores. Un fallo puede ocurrir durante pull, create o start.
El command resultante de ENTRYPOINT, CMD y overrides se convierte en el proceso principal dentro del contenedor.
Texto
Copiar container namespace
PID 1 → node server.jsEl proceso principal tiene responsabilidades importantes:
recibir señales de terminación;
cerrar conexiones y trabajo pendiente;
recolectar procesos hijos cuando corresponda;
devolver un exit code significativo.
Si el proceso principal crea un daemon en background y termina, el contenedor también termina aunque el daemon hijo intentara seguir ejecutándose.
Bash
Copiar #!/bin/sh
node server.js & El script inicia Node en background y luego sale. El contenedor deja de tener un proceso principal activo.
Una ejecución correcta mantiene el servidor en foreground:
Bash
Copiar exec node server.jsexec reemplaza el shell y permite que Node reciba señales directamente.
La configuración existe, pero el proceso todavía no inició.
El proceso principal está activo.
Los procesos fueron suspendidos mediante mecanismos del kernel. No equivale a shutdown; memoria y recursos siguen asignados.
Bash
Copiar docker pause api
docker unpause apiDocker intenta iniciar nuevamente el contenedor según su restart policy.
El proceso principal terminó. La instancia y su writable layer todavía existen.
Docker no pudo completar correctamente alguna operación de terminación o limpieza. Requiere investigar daemon, runtime o recursos asociados.
El estado resumido no explica la causa. Utiliza:
Bash
Copiar docker inspect api --format '{{json .State}}' Un proceso puede finalizar porque:
completó correctamente una tarea;
recibió una señal;
encontró un error de aplicación;
no pudo leer configuración;
fue terminado por OOM;
el runtime falló;
el operador ejecutó stop o kill;
el host se apagó.
Todas producen un contenedor no running, pero requieren respuestas distintas.
Bash
Copiar docker stop api
Docker envía la señal de parada configurada, normalmente SIGTERM en Linux.
Espera el timeout establecido.
Si el proceso sigue activo, envía SIGKILL.
Registra el estado y exit code.
Puede configurarse el tiempo:
Bash
Copiar docker stop --time 30 apiLa aplicación debe terminar antes del límite. En una API, un graceful shutdown puede:
dejar de aceptar requests nuevas;
completar requests en curso dentro de un límite;
detener consumidores o workers;
cerrar pools de base de datos;
hacer flush de buffers;
finalizar con exit code apropiado.
Un timeout más largo no arregla una aplicación que ignora señales o nunca deja de aceptar trabajo.
Bash
Copiar docker kill apiPor defecto envía SIGKILL, que no puede manejarse ni ignorarse. El proceso termina inmediatamente sin oportunidad de limpiar.
También puede enviar otra señal:
Bash
Copiar docker kill --signal = SIGHUP apiNo utilices kill como equivalente rutinario de stop. Puede interrumpir escrituras, jobs y transacciones.
El exit code describe cómo terminó el proceso, aunque debe interpretarse junto con señales y estado.
Bash
Copiar docker inspect api --format '{{.State.ExitCode}}'
0: finalización considerada exitosa por el proceso;
1: error general de la aplicación;
126: command encontrado pero no ejecutable;
127: command no encontrado;
128 + señal: convención común para terminación por señal;
137: frecuentemente SIGKILL, que puede venir de OOM o acción del operador;
143: frecuentemente SIGTERM.
No diagnostiques únicamente con el número. Para 137, revisa:
Bash
Copiar docker inspect api --format '{{.State.OOMKilled}}'
docker events --since 10m
docker logs apiUn 137 con OOMKilled=false puede indicar docker kill, timeout de stop o terminación externa.
Cuando un contenedor supera su límite de memoria, el kernel puede terminar procesos. Docker puede registrar:
JSON
Copiar { "OOMKilled" : true , "ExitCode" : 137 }
la restart policy puede crear un loop;
el trabajo en memoria se pierde;
la aplicación puede parecer “inestable” cuando el problema real es capacidad o memory leak.
confirmar OOM;
medir uso y límites;
analizar heap y memoria nativa;
ajustar concurrencia o consumo;
cambiar capacidad solo con evidencia;
verificar comportamiento tras reinicio.
Docker puede intentar iniciar automáticamente un contenedor cuando su proceso termina o el daemon reinicia, según la política.
No reinicia automáticamente. Es el valor predeterminado.
Reinicia si el proceso termina con exit code no cero. Puede aceptar un máximo de reintentos:
Bash
Copiar docker run --restart on-failure:5 my-apiUna terminación exitosa no dispara reinicio.
Intenta mantener el contenedor iniciado, sujeto al comportamiento y reglas del Engine. Si el operador lo detiene manualmente, existen matices sobre cuándo volverá a iniciarse; verifica la versión y operación esperada.
Similar a always, pero conserva la intención de una parada manual frente a reinicios posteriores del daemon.
recuperación de crashes transitorios del proceso;
reinicio tras ciertos reinicios del daemon o host;
reducción de intervención manual en un solo Engine.
pérdida completa del host;
corrupción de datos;
una configuración inválida persistente;
dependencia externa caída;
despliegue de una versión defectuosa;
redundancia o balanceo;
escalado;
alta disponibilidad multi-host.
Una restart policy puede convertir un error determinista en un crash loop que consume CPU, genera logs y oculta la causa.
Bash
Copiar docker restart apiDetiene e inicia la misma instancia :
conserva configuración;
conserva nombre;
conserva writable layer;
conserva mounts y networks;
ejecuta nuevamente el mismo artefacto y command.
Bash
Copiar docker rm -f api
docker run --name api my-api:1.1
nueva writable layer;
puede usar otra imagen;
puede cambiar configuración;
recibe otra identidad interna;
vuelve a aplicar mounts y networks.
En despliegues inmutables, actualizar significa normalmente crear un contenedor nuevo desde una imagen nueva, no instalar cambios dentro del existente.
Solo puede eliminar normalmente un contenedor detenido. --force termina y elimina.
desaparece la metadata de la instancia;
desaparece su writable layer;
se liberan relaciones administradas;
named volumes normalmente permanecen;
bind mounts apuntan a datos del host y permanecen;
la imagen permanece mientras no se elimine aparte.
Por eso “borrar el contenedor” no debe confundirse con “borrar todos los datos”. Depende de dónde vivía el estado.
Bash
Copiar docker run --rm alpine echo holaEl contenedor se elimina cuando termina. Es apropiado para:
comandos de una sola ejecución;
herramientas CLI;
conversiones;
pruebas efímeras;
jobs cuyo resultado sale por stdout o un mount.
Puede ser inconveniente durante diagnóstico porque desaparecen inspect, exit metadata y writable layer. Los logs pueden perderse si no están centralizados.
Bash
Copiar docker run my-apiLa terminal recibe stdout/stderr y puede enviar señales o input según configuración.
Bash
Copiar docker run -d --name api my-apiLa CLI devuelve el ID y el proceso continúa.
Detached no convierte la aplicación en un daemon interno ni cambia el lifecycle. El proceso principal sigue determinando la vida del contenedor.
JavaScript
Copiar import http from 'node:http' ;
const server = http. createServer ( ( request, response ) => {
response. end ( 'ok' ) ;
} ) ;
server. listen ( 3000 , '0.0.0.0' ) ;
const shutdown = ( signal ) => {
console. log ( ` Received ${ signal} ` ) ;
server. close ( ( error ) => {
if ( error) {
console. error ( error) ;
process. exitCode = 1 ;
}
} ) ;
setTimeout ( ( ) => {
console. error ( 'Graceful shutdown timed out' ) ;
process. exit ( 1 ) ;
} , 25_000 ) . unref ( ) ;
} ;
process. on ( 'SIGTERM' , ( ) => shutdown ( 'SIGTERM' ) ) ;
process. on ( 'SIGINT' , ( ) => shutdown ( 'SIGINT' ) ) ; Flujo durante docker stop --time 30 api:
Docker envía SIGTERM.
Node ejecuta el handler.
El server deja de aceptar conexiones nuevas.
Completa conexiones activas.
El event loop queda sin trabajo.
Node termina antes de los 30 segundos.
Docker registra la salida.
En producción también deben cerrarse pools, consumers y procesos hijos. El ejemplo simplifica esos elementos.
No todo contenedor debe permanecer vivo indefinidamente.
Una API o worker suele esperar eventos y permanecer running.
Una migración, backup o batch debe terminar al completar:
Bash
Copiar docker run --rm my-app npm run migrateUn exit code 0 indica éxito del job. Forzarlo a permanecer vivo con tail -f /dev/null oculta su lifecycle real.
Un contenedor puede estar running y unhealthy.
Texto
Copiar proceso activo ≠ aplicación disponibleEl proceso puede aceptar conexiones pero fallar al acceder a dependencias. Docker mantiene el health status como señal adicional; no termina automáticamente el proceso solo porque esté unhealthy, salvo que otra herramienta tome una decisión.
Bash
Copiar docker ps -a
docker logs < name>
docker inspect < name> docker ps sin -a solo muestra running containers.
Puede desaparecer antes de inspeccionarlo. Durante diagnóstico, ejecuta temporalmente sin --rm o centraliza logs y eventos.
La shell form de CMD o un script incorrecto puede impedir que la aplicación reciba SIGTERM. Usa exec form y exec en scripts.
PID 1 tiene comportamiento especial respecto a recolección de hijos. Usa un init mínimo con --init si la aplicación no administra correctamente ese patrón.
La restart policy vuelve a ejecutar la misma configuración fallida. Limita reintentos cuando corresponda, alerta sobre loops y corrige la causa.
No hay garantía de graceful shutdown suficiente. Los datos durables deben tolerar crashes y tener recuperación.
Por qué ocurre: se cree que un contenedor debe estar siempre running.
Consecuencia: se oculta que el proceso real terminó y health deja de representar la aplicación.
Corrección: ejecuta el proceso real en foreground o modela el trabajo como job.
Consecuencia: pérdida de trabajo y ausencia de shutdown limpio.
Corrección: usa stop, observa señales y reserva kill para procesos bloqueados.
Consecuencia: el sistema sigue dependiendo de un solo host.
Corrección: define redundancia, scheduler, almacenamiento y failover según el SLO.
Consecuencia: la writable layer mutable se convierte en fuente de verdad no reproducible.
Corrección: modifica source o Dockerfile, reconstruye y recrea.
Consecuencia: pipelines consideran exitoso un job fallido o reinician errores deterministas.
Corrección: propaga códigos significativos y monitoriza la causa.
Ante un contenedor detenido:
Bash
Copiar docker ps -a --filter name = '^/api$'
docker inspect api --format '{{json .State}}'
docker logs --tail 200 api
docker events --since 15m --filter container = api
¿Se creó correctamente?
¿Qué command intentó ejecutar?
¿El proceso llegó a iniciar?
¿Qué exit code y señal aparecen?
¿Fue OOMKilled?
¿La restart policy produjo un loop?
¿Los logs muestran configuración o dependencia fallida?
¿La terminación fue manual?
No ejecutes repetidamente restart antes de conservar evidencia.
Bash
Copiar docker create --name lifecycle-demo alpine sleep 60
docker inspect lifecycle-demo --format '{{.State.Status}}'
docker start lifecycle-demo
docker inspect lifecycle-demo --format '{{.State.Status}}'
docker stop lifecycle-demo
docker inspect lifecycle-demo --format '{{json .State}}'
docker rm lifecycle-demoBash
Copiar docker run --name success alpine sh -c 'exit 0'
docker run --name failure alpine sh -c 'exit 42'
docker inspect success --format '{{.State.ExitCode}}'
docker inspect failure --format '{{.State.ExitCode}}'
docker rm success failureCrea un archivo en la writable layer, reinicia y comprueba que continúa. Después elimina y crea otro contenedor desde la misma imagen: el archivo ya no existe.
run combina creación y arranque.
El proceso principal determina la vida del contenedor.
Un objeto exited conserva metadata y writable layer hasta ser eliminado.
stop intenta graceful shutdown; kill termina de forma inmediata por defecto.
Exit code, señal, OOM y logs deben interpretarse juntos.
Restart conserva la instancia; recrear produce una nueva.
Restart policies recuperan procesos locales, no proporcionan HA.
Eliminar el contenedor borra su writable layer, no necesariamente volumes o datos externos.
Comprueba lo aprendido
Explica qué operaciones combina docker run y en cuál podría fallar cada tipo de error.
¿Por qué un proceso en background puede provocar que el contenedor termine?
Diseña un graceful shutdown para una API con requests, workers y base de datos.
¿Cómo distinguirías un OOM de un docker kill si ambos producen exit code 137?
¿Qué diferencias observables existen entre reiniciar y recrear?
¿Cuándo usarías --rm y cuándo dificultaría el diagnóstico?
¿Por qué una restart policy puede empeorar un error de configuración?
Filesystem y writable layer , para estudiar qué ocurre con los cambios de archivos durante este lifecycle y qué estado desaparece al reemplazar la instancia.