Guía9 min

depends_on healthy: el agente no arranca antes que Postgres

Resumen

Cómo no crashear el bot en el primer segundo: depends_on condition service_healthy, pg_isready y start_period. Distinto del /health del agente. Cifras oficiales Compose, Dockerfile HEALTHCHECK y Postgres 18, curl 3 de septiembre de 2026. Fly y Railway no equivalen a depends_on.

Docker
Agente esperando a que Postgres pase healthcheck antes de arrancar

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

Compose depends_on: postgres solo espera a que el contenedor exista, no a que acepte SQL. El agente arranca, ECONNREFUSED, restart lo tira 20 veces. condition: service_healthy espera el healthcheck de la DB. Fuentes oficiales consultadas el 3 de septiembre de 2026.

Healthchecks del agente son liveness del bot. Esta guía es orden de arranque.

La regla

depends_on:
  postgres:
    condition: service_healthy
    restart: true

Postgres tiene healthcheck con pg_isready. start_period cubre el initdb lento. El agente no tiene un sleep 10 en el entrypoint.

La sintaxis corta (depends_on: [db, redis]) es service_started: Compose crea db y redis antes que web, y los apaga al revés. No espera “healthy”. La forma larga sí.

Condiciones oficiales

Consultado en Compose services el 3 de septiembre de 2026:

CondiciónQué esperaSuficiente para SQL
(nada / short)contenedor creadono
service_startedmismo que shortno
service_healthyhealthcheck en healthysí si el check es pg_isready
service_completed_successfullyjob exit 0migraciones one-shot

required default true (Compose 2.20.0+). required: false solo avisa si falta el servicio. Un Postgres de prod no es opcional.

restart: true en el depends_on de web→db: si Compose reinicia db (docker compose restart), también reinicia web. No es el restart: del servicio (unless-stopped).

También ordenan arranque: links, volumes_from y network_mode: "service:...". pre_start corre después de satisfacer depends_on.

Healthcheck de la DB

No copies el del agente (node -e http). Ejemplo oficial de startup-order (Postgres 18):

db:
  image: postgres:18
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
    interval: 10s
    retries: 5
    start_period: 30s
    timeout: 10s

CMD-SHELL usa /bin/sh en Linux. Un string suelto equivale a CMD-SHELL. Lista: primer ítem NONE, CMD o CMD-SHELL. NONE o disable: true apagan el HEALTHCHECK de la imagen. start_interval (Compose 2.20.2+) es el intervalo durante start_period; el ejemplo de referencia de services usa start_period: 40s y start_interval: 5s.

pg_isready (Postgres 18, curl 2026-09-03)

ExitSignificado
0acepta conexiones
1rechaza (arrancando)
2sin respuesta
3no intentó (parámetros inválidos)

Timeout default 3 s; 0 lo desactiva. Puerto default 5432. No hace falta user/password/dbname correctos para el status; valores malos sí dejan ruido en el log del server.

Un wget al 5432 no es SQL ready. Redis: redis-cli ping (PONG).

HEALTHCHECK de imagen vs Compose

Dockerfile (defaults oficiales):

FlagDefault
--interval30s
--timeout30s
--start-period0s
--start-interval5s
--retries3

Estado extra: startinghealthy al primer pass → unhealthy tras N fallos seguidos. Exit del probe: 0 ok, 1 unhealthy, 2 reservado. Solo el último HEALTHCHECK cuenta. HEALTHCHECK NONE hereda nada. stdout/stderr del probe: primeros 4096 bytes en docker inspect.

--start-period default 0s es el bug de initdb: el countdown de retries empieza ya. En Compose pon start_period medido (30–60 s en volumen vacío), no 2 s de fe.

Lo que no hace

No sustituye red privada. No espera “migraciones”. Si migran en el agente: job con service_completed_successfully antes, o retry de connect (backoff). Cero sleep.

Fly/Railway no son Compose depends_on. Un retry en el cliente es el portable.

Fly y Railway (no es depends_on)

depends_on started versus service_healthy

Railway healthcheck (guía oficial, curl 2026-09-03): espera 2xx en el path del servicio que despliegas, luego corta el deploy anterior. No vigila el endpoint después de live. Timeout default 300 s (5 min); se sube con RAILWAY_HEALTHCHECK_TIMEOUT_SEC. Eso no espera a que Postgres acepte SQL: espera a que tu HTTP esté 2xx. Si el bot crashea al conectar, el deploy falla a los 300 s. El health del agente no debe pegarle al LLM; el de la DB sí es pg_isready en Compose.

Fly [[http_service.checks]] de ejemplo: grace_period = "10s", interval = "30s", timeout = "5s", GET /. Corre por la red privada de la Machine, no por appname.fly.dev. Tampoco es “espera al Postgres de al lado”: son dos Machines. kill_timeout default 5 s, tope 300 s — eso es apagado, no arranque.

Kubernetes: init, no depends_on

Init containers corren hasta completar. Cada uno debe salir 0 antes del siguiente; varios van en serie. Si uno falla, kubelet reintenta hasta que sale, salvo restartPolicy: Never (el Pod queda failed). Equivalente operativo: un init pg_isready loop, no sleep 30 en el agente.

Errores comunes

Agente en crash loop mientras Postgres initdb

SíntomaCausaFix
ECONNREFUSED × Nshort syntaxservice_healthy
healthy eternocheck wget/HTTPpg_isready exit 0
sleep 30parchestart_period en la DB
Fly “depends_on”no existeretry en app
migrate en el webhookprimer requestjob service_completed_successfully
required: false en postgresCompose avisa y siguedefault true
imagen con HEALTHCHECK HTTPhereda maldisable: true + pg_isready

Relación con el resto

Profiles y scale

depends_on a un servicio con profiles: [tools] que no está up = Compose se queja si required es true. El postgres de prod no es profile opcional.

scale: 0 de postgres rompe el agente. El kill switch del bot no apaga la DB.

Si healthy nunca llega, up se queda. start_period + retries deben cubrir initdb real (mide una vez). Con el ejemplo oficial: 30 s de gracia + 5 retries × 10 s.

restart: always esconde el race: parece “al final funciona”. Cuenta restarts del primer boot.

Compose v1 sin condition: upgrade. El campo vive en services.

SQLite en un solo contenedor: no hay depends_on. El volumen basta.

Checklist

  • condition: service_healthy a postgres/redis
  • Check real (pg_isready, exit 0/1/2/3)
  • start_period medido; --start-period de imagen no es 0s a ciegas
  • Cero sleep en el agente
  • Migraciones en job service_completed_successfully, no en el webhook
  • Ensayo: borra volumen, compose up; el agente loguea “connected” después de healthy

FAQ

¿service_started? Mejor que nada, peor que healthy.

¿Un solo contenedor sqlite? No hay depends_on.

¿K8s? Init container sequential, no Compose.

¿Railway espera la DB? No. Espera 2xx de tu servicio, 300 s default, y deja de mirar al ir live.

Siguiente paso: si el orden ya es healthy y igual falla SQL, red privada y secretos. Sin runtime: curso.