Healthchecks y depends_on en Docker Compose | Nicolás Garzón
Texto
Copiar created → running → initializing → ready
↘ unhealthyHealthchecks producen una señal observable. depends_on puede coordinar parte del arranque, pero la resiliencia real debe vivir también en la aplicación.
El proceso principal existe.
El comando de healthcheck ha tenido éxito según interval, timeout y retries.
El servicio puede atender la función para la que recibe tráfico.
Docker Engine expone health, pero no separa de forma universal liveness y readiness como otros orquestadores. Diseña endpoints con intención y comprende cómo los consume cada plataforma.
docker
Copiar HEALTHCHECK --interval=10s --timeout=3s --retries=5 --start-period=20s \
CMD ["node", "healthcheck.js"]YAML
Copiar services :
api :
healthcheck :
test : [ "CMD" , "node" , "healthcheck.js" ]
interval : 10s
timeout : 3s
retries : 5
start_period : 20sEl comando se ejecuta dentro del container. Debe existir en la image final.
Frecuencia entre comprobaciones. Un valor demasiado corto añade carga y ruido.
Tiempo máximo de cada intento. Sin límite, un check bloqueado puede acumular procesos.
Fallos consecutivos antes de marcar unhealthy. Ajusta según variabilidad real.
Ventana para inicialización sin penalizar de la misma manera los fallos tempranos. No debe esconder arranques indefinidos.
Versiones modernas pueden soportar una frecuencia específica durante startup. Verifica compatibilidad del Engine/Compose objetivo.
YAML
Copiar healthcheck :
test : [ "CMD" , "node" , "healthcheck.js" ] YAML
Copiar healthcheck :
test : [ "CMD-SHELL" , "pg_isready -U app -d app || exit 1" ] La shell form permite operadores, pero depende de que exista shell y de quoting correcto.
Desactivar un health heredado:
YAML
Copiar healthcheck :
disable : true Solo cuando existe una razón y otra señal de salud.
rápido;
local;
determinista;
sin efectos secundarios;
con timeout;
representativo;
seguro para ejecutarse repetidamente.
Para una API, puede comprobar que:
event loop responde;
configuración cargó;
servidor acepta requests;
componentes críticos internos están inicializados.
No es necesario consultar todas las dependencias externas en cada liveness check. Si una API se marca muerta porque un proveedor externo cae, un restart loop puede empeorar el incidente.
Pregunta: “¿el proceso está irrecuperablemente bloqueado y debe reiniciarse?”
Debe evitar depender de servicios externos transitorios.
Pregunta: “¿puede recibir tráfico ahora?”
migrations terminadas;
pools inicializados;
cache caliente mínima;
capacidad para atender requests.
Pregunta: “¿terminó la inicialización inicial?”
En Docker Engine, estas señales pueden modelarse parcialmente con health y start_period; un orquestador ofrece semántica más rica.
Docker registra healthy o unhealthy, pero una restart policy no siempre reinicia simplemente por estado unhealthy. El proceso puede continuar running.
alertar;
retirar tráfico mediante proxy/orquestador;
reiniciar de forma controlada;
investigar el check.
No asumas auto-healing sin verificar la plataforma.
YAML
Copiar services :
api :
depends_on :
db :
condition : service_healthyPuede esperar a que db sea healthy antes de iniciar api en Compose compatible.
Otras condiciones pueden modelar inicio o finalización exitosa de jobs según versión:
YAML
Copiar services :
api :
depends_on :
migrate :
condition : service_completed_successfullyVerifica soporte exacto en la versión desplegada.
database puede reiniciar;
red puede fallar;
credencial puede rotar;
DNS puede cambiar;
pool puede romperse;
health puede degradarse.
La aplicación debe implementar retries, timeouts, circuit breaking cuando corresponda y reconexión.
Bash
Copiar sleep 20 && node server.js
en un equipo db tarda 5 s;
en CI tarda 40 s;
no verifica la condición real;
retrasa incluso cuando está listo;
no ayuda ante reinicios posteriores;
oculta errores de inicialización.
Espera señales reales y diseña resiliencia.
Un cliente de database puede:
Texto
Copiar intento 1 → falla
espera 500 ms
intento 2 → falla
espera 1 s
intento 3 → falla
espera 2 s + jitter
...
límite máximo;
timeout por intento;
jitter para evitar thundering herd;
logs sin credenciales;
métricas;
comportamiento final claro.
Retries infinitos pueden mantener un container running pero inútil. Decide si debe quedar no ready o terminar para ser reemplazado.
YAML
Copiar services :
db :
image : postgres: 17
environment :
POSTGRES_USER : app
POSTGRES_PASSWORD : app
POSTGRES_DB : app
healthcheck :
test : [ "CMD-SHELL" , "pg_isready -U app -d app" ]
interval : 5s
timeout : 3s
retries : 10
migrate :
image : my- api
command : [ "npm" , "run" , "migrate" ]
depends_on :
db :
condition : service_healthy
restart : "no"
api :
image : my- api
depends_on :
migrate :
condition : service_completed_successfully
credenciales son solo ejemplo de desarrollo;
migrations deben ser compatibles con varias instancias;
Compose no coordina varios hosts;
API todavía necesita reconectar si db cae después.
JSON
Copiar {
"status" : "ok" ,
"version" : "1.4.0" ,
"checks" : {
"eventLoop" : "ok" ,
"database" : "ok"
}
}
passwords;
connection strings;
stack traces;
topología interna sensible;
tokens.
Un endpoint público puede devolver información mínima; detalles pueden quedar en telemetry autenticada.
Agregar curl solo para health aumenta paquetes. Alternativas:
script con Node/Python ya presente;
binary pequeño;
check TCP específico;
herramienta incluida por la base.
JavaScript
Copiar import http from 'node:http' ;
const request = http. get ( 'http://127.0.0.1:3000/health' , ( response ) => {
process. exit ( response. statusCode === 200 ? 0 : 1 ) ;
} ) ;
request. setTimeout ( 2_000 , ( ) => {
request. destroy ( ) ;
process. exit ( 1 ) ;
} ) ;
request. on ( 'error' , ( ) => process. exit ( 1 ) ) ; Bash
Copiar docker inspect api --format '{{json .State.Health}}'
docker compose ps
docker compose logs apiLa salida de intentos ayuda a distinguir:
command inexistente;
timeout;
endpoint incorrecto;
dependencia caída;
permisos;
check demasiado estricto.
Ejecuta el command manualmente dentro del mismo entorno cuando sea posible.
Consulta una tabla pesada cada segundo. El check se convierte en causa del incidente.
Liveness falla y reinicia todas las réplicas, creando tormenta.
start_period insuficiente marca unhealthy antes de compilar/cargar modelos.
El container parece healthy aunque la aplicación no responda.
El command usa sh o curl inexistente.
Checks muy sensibles producen flapping.
Envía tráfico durante inicialización.
No observa condición real.
Amplifica fallos externos.
No cubre fallos después del startup.
Running, healthy y ready no son equivalentes.
Health es una señal, no una garantía permanente.
depends_on ayuda al arranque, no a la resiliencia continua.
La aplicación necesita retries y reconexión.
Liveness no debe depender indiscriminadamente de servicios externos.
Checks deben ser baratos, seguros y observables.
Comprueba lo aprendido
Diseña liveness, readiness y startup para una API real.
¿Por qué una database caída no siempre debe hacer fallar liveness?
¿Qué no cubre depends_on después del arranque?
Reemplaza un sleep 20 por una estrategia basada en señales.
¿Cómo diagnosticarías un container unhealthy pero running?
Profiles, overrides y Compose Watch , donde se modelan variaciones sin duplicar stacks enteros.