Saltar al contenido principal

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:

  1. Compose inicia db.
  2. El proceso de PostgreSQL queda running.
  3. PostgreSQL continúa inicializándose.
  4. Compose inicia app.
  5. app intenta conectar una sola vez.
  6. 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 de unhealthy;
  • 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_on corto espera readiness;
  • tratar running como healthy;
  • comprobar solo que el proceso existe;
  • usar sleep 30 como 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:

  1. explica qué garantiza y qué no;
  2. añade a db un healthcheck con pg_isready;
  3. justifica interval, timeout, retries y start_period;
  4. valida con docker compose config;
  5. observa starting y healthy;
  6. condiciona app con service_healthy;
  7. comprueba el orden mediante estado y logs;
  8. provoca unhealthy cambiando temporalmente el check a pg_isready -h 127.0.0.1 -p 65432 -U tastematch -d tastematch, como en el laboratorio;
  9. conserva evidencia de running + unhealthy y del historial en State.Health; restaura el check correcto y comprueba que vuelve a healthy;
  10. explica qué ocurriría si db falla después del arranque;
  11. propone cómo debería reaccionar conceptualmente app;
  12. explica por qué sleep 30 no es equivalente.