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.

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ón | Qué espera | Suficiente para SQL |
|---|---|---|
| (nada / short) | contenedor creado | no |
service_started | mismo que short | no |
service_healthy | healthcheck en healthy | sí si el check es pg_isready |
service_completed_successfully | job exit 0 | migraciones 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)
| Exit | Significado |
|---|---|
| 0 | acepta conexiones |
| 1 | rechaza (arrancando) |
| 2 | sin respuesta |
| 3 | no 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):
| Flag | Default |
|---|---|
--interval | 30s |
--timeout | 30s |
--start-period | 0s |
--start-interval | 5s |
--retries | 3 |
Estado extra: starting → healthy 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)

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

| Síntoma | Causa | Fix |
|---|---|---|
ECONNREFUSED × N | short syntax | service_healthy |
| healthy eterno | check wget/HTTP | pg_isready exit 0 |
sleep 30 | parche | start_period en la DB |
| Fly “depends_on” | no existe | retry en app |
| migrate en el webhook | primer request | job service_completed_successfully |
required: false en postgres | Compose avisa y sigue | default true |
| imagen con HEALTHCHECK HTTP | hereda mal | disable: true + pg_isready |
Relación con el resto
- Restart: unless-stopped no espera DB.
- Bind: 0.0.0.0.
- Health del bot: healthchecks.
- Init PID 1: tini no espera DB.
- Secretos: env.
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_healthya postgres/redis - Check real (
pg_isready, exit 0/1/2/3) -
start_periodmedido;--start-periodde imagen no es 0s a ciegas - Cero
sleepen 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.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



