Dependencias y healthchecks
Levantar app y db no significa que ambos puedan trabajar inmediatamente. Docker puede haber iniciado el proceso de PostgreSQL mientras la base todavía prepara sus archivos y conexiones.
created → running → inicializando → ready
running describe el proceso principal del contenedor. ready describe si el servicio puede cumplir la función que esperamos. No son equivalentes.
El fallo que queremos entender
Imagina esta secuencia:
- Compose inicia
db. - El proceso de PostgreSQL queda
running. - PostgreSQL continúa inicializándose.
- Compose inicia
app. appintenta conectar una sola vez.- La conexión falla y la aplicación termina.
El problema no es necesariamente la red ni la contraseña. La dependencia existe, pero aún no estaba disponible.
depends_on: expresar orden
Reutiliza el app.js y el Dockerfile de DK2-02. Este primer fragmento destaca la dependencia: conserva la configuración de PostgreSQL de la unidad anterior al incorporarlo a tu proyecto.
services:
app:
build: .
depends_on:
- db
db:
image: postgres:17-alpine
La forma corta expresa que app depende de db y ordena la creación/inicio básico. No significa por sí sola:
PostgreSQL acepta conexiones
contenedor db iniciado ≠ servicio db preparado
Una señal de salud
Un healthcheck ejecuta una comprobación para clasificar la salud del servicio. Cuando existe, podemos observar:
running + starting
running + healthy
running + unhealthy
Un contenedor puede seguir running y estar unhealthy. Docker no conoce automáticamente qué significa «preparado» para nuestra aplicación; debemos elegir una comprobación significativa.
Healthcheck real para PostgreSQL
La imagen postgres:17-alpine incluye pg_isready, herramienta específica para comprobar si PostgreSQL acepta conexiones. No verifica que un usuario o una base concretos sean válidos, ni que la aplicación pueda ejecutar sus consultas. Configura:
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: tastematch
POSTGRES_USER: tastematch
POSTGRES_PASSWORD: laboratorio
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tastematch -d tastematch"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
Los valores son de laboratorio y deben comprobarse con la carga real:
test: orden que decide éxito o fallo;interval: tiempo entre comprobaciones;timeout: máximo permitido para cada intento;retries: fallos consecutivos tolerados antes deunhealthy;start_period: margen inicial durante el que el servicio puede arrancar sin penalizar normalmente fallos como en el periodo ordinario.
start_period da un margen inicial para los fallos; no convierte automáticamente el servicio en healthy. Un check exitoso puede marcarlo como saludable antes de que termine ese periodo, y los fallos posteriores ya cuentan. No debe esconder un test incorrecto ni un arranque indefinido.
Esperar a healthy
Una vez que db tiene healthcheck, relaciona la dependencia:
services:
app:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: tastematch
POSTGRES_USER: tastematch
POSTGRES_PASSWORD: laboratorio
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tastematch -d tastematch"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
La intención es:
db inicia → starting → healthy → app puede iniciar
Valida la sintaxis con la versión de Docker Compose utilizada en el laboratorio:
docker compose config
docker compose up -d
docker compose ps
Para observar starting mientras up -d espera a la dependencia, consulta docker compose ps desde otra terminal situada en la misma carpeta. Después compara la salud de db con el arranque de app.
Observar, no asumir
docker compose ps
docker compose logs db
docker compose logs app
Para localizar el detalle del healthcheck:
docker inspect <contenedor-db>
Busca State.Health; no necesitas interpretar todo el JSON. Relaciona el estado con los logs y con el comportamiento de la aplicación.
Provocar unhealthy
Hazlo solo en el laboratorio y parte de db en estado healthy. Cambia temporalmente el test para consultar 127.0.0.1:65432, un puerto donde PostgreSQL no escucha. Es una inyección deliberada de fallo en la comprobación, no la configuración correcta del servicio:
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 65432 -U tastematch -d tastematch"]
interval: 5s
timeout: 3s
retries: 2
start_period: 2s
pg_isready devuelve 0 cuando el servidor acepta conexiones y 2 si no obtiene respuesta del destino, como en esta prueba. No utilices una base o un usuario inexistentes para provocar el fallo: el servidor puede seguir considerándose disponible.
Aplica el cambio y observa:
docker compose up -d
docker compose ps
docker inspect <contenedor-db>
docker compose logs db
Espera el número de intentos necesario. Debes poder ver running + unhealthy. En State.Health, revisa Status, FailingStreak y el historial Log: sus entradas muestran el código de salida y el mensaje no response. El proceso de PostgreSQL puede seguir funcionando; lo que hemos roto deliberadamente es el destino del check.
Con service_healthy, up -d puede terminar indicando que la dependencia está unhealthy. Es parte del fallo esperado; continúa con la inspección. No interpretes esto como una garantía de que Compose detenga una app que ya estaba ejecutándose.
Restaura el test normal y sus valores originales: pg_isready -U tastematch -d tastematch, interval: 5s, timeout: 3s, retries: 5 y start_period: 10s. Aplica de nuevo docker compose up -d y comprueba con ps e inspect que db vuelve a healthy y el historial registra checks exitosos.
Qué no resuelve service_healthy
La condición controla el gate de arranque estudiado. No convierte la aplicación en resiliente:
db healthy → app inicia → diez minutos después db falla
Compose no reescribe automáticamente la lógica de app. La aplicación debe considerar:
- timeouts razonables;
- reintentos limitados;
- reconexión cuando corresponda;
- errores comprensibles;
- no repetir operaciones no seguras a ciegas.
Aquí solo introducimos estas responsabilidades; no diseñamos patrones avanzados de resiliencia.
Por qué sleep 30 no basta
sleep 30
no pregunta por el estado real:
- si PostgreSQL tarda 5 segundos, desperdicia 25;
- si tarda 45, la aplicación falla;
- si nunca arranca, la espera termina igualmente.
Una espera fija puede servir en una prueba puntual, pero no sustituye una comprobación significativa.
Diseñar un buen healthcheck
Debe ser:
- significativo: comprueba la función necesaria;
- ligero: no añade carga importante;
- rápido: termina dentro del timeout;
- estable: evita sensibilidad irrelevante;
- no destructivo: no modifica datos de negocio.
Para HTTP, un endpoint como /health puede ser apropiado si su semántica está definida. Comprobar solo que existe el proceso puede ser demasiado superficial; consultar diez dependencias externas puede ser demasiado profundo.
Healthcheck tampoco equivale a monitorización, autohealing o alta disponibilidad.
Diagnóstico guiado
¿contenedor creado?
→ ¿running?
→ ¿health starting/healthy/unhealthy?
→ ¿qué dice el test?
→ ¿qué dicen los logs?
→ ¿falló el arranque o el servicio posteriormente?
docker compose ps
docker compose logs db
docker compose logs app
docker inspect <contenedor-db>
Errores habituales:
- creer que
depends_oncorto espera readiness; - tratar
runningcomohealthy; - comprobar solo que el proceso existe;
- usar
sleep 30como solución universal; - ejecutar tests demasiado frecuentes o costosos;
- modificar datos desde el healthcheck;
- hacer depender la salud de demasiados sistemas externos;
- confundir healthcheck con monitorización o alta disponibilidad.
Mini reto
Recibes app y db con depends_on corto:
- explica qué garantiza y qué no;
- añade a
dbun healthcheck conpg_isready; - justifica
interval,timeout,retriesystart_period; - valida con
docker compose config; - observa
startingyhealthy; - condiciona
appconservice_healthy; - comprueba el orden mediante estado y logs;
- provoca
unhealthycambiando temporalmente el check apg_isready -h 127.0.0.1 -p 65432 -U tastematch -d tastematch, como en el laboratorio; - conserva evidencia de
running + unhealthyy del historial enState.Health; restaura el check correcto y comprueba que vuelve ahealthy; - explica qué ocurriría si
dbfalla después del arranque; - propone cómo debería reaccionar conceptualmente
app; - explica por qué
sleep 30no es equivalente.