Healthchecks para agentes IA en producción: Docker, Fly, Railway
Resumen
Cómo diseñar un endpoint /health que no mienta: liveness vs readiness para agentes, HEALTHCHECK en Docker, checks de Fly.io, healthchecks de Railway que solo corren en el deploy, y los errores que dejan tráfico en un proceso vivo pero sordo.

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.
El agente ya está en Docker, Railway o Fly. El deploy “verde” no significa que responda. Un proceso vivo que no acepta webhooks es un 502 con el contenedor Up. El healthcheck es el contrato entre tu agente y la plataforma: si miente, la plataforma miente. Las fuentes oficiales fueron consultadas el 3 de septiembre de 2026.
Definición citable
Liveness: el proceso no está trabado. Si falla, hay que reiniciarlo. Readiness: el proceso puede aceptar tráfico ahora (modelo listo, conexión a BD, cola no saturada). Si falla, hay que dejar de mandarle requests, no necesariamente matarlo. Kubernetes documenta esa separación en probes de liveness, readiness y startup; las plataformas de builders suelen mezclarlas en un solo /health. Mezclarlas mal es el bug más caro: un check que pega al LLM en cada probe convierte un fallo del proveedor en un restart loop.
Qué debe hacer /health
Responde 200 rápido, sin llamar al modelo.
app.get("/health", (_req, res) => {
res.status(200).json({ ok: true });
});
Eso es liveness. Si quieres readiness, añade una comprobación local (sqlite abre, Redis PING, el puerto interno responde) con timeout corto. Nunca:
- Completar un chat de prueba contra OpenAI.
- Esperar a que la cola se vacíe.
- Depender de un servicio externo que tú no controlas.
Un agente “listo” es “acepta el webhook y encola”. No es “el LLM contestó”.
El check también es parte del contrato con CI/CD: si el job de deploy espera un 200 y /health llama al modelo, un corte de OpenAI bloquea Production. El pipeline debe pegar al mismo endpoint barato que Docker y Railway.
Cómo lo usa cada plataforma

| Plataforma | Qué hace el check | Cuándo corre | Si falla |
|---|---|---|---|
Docker HEALTHCHECK | Corre un comando dentro del contenedor | Desde que arranca; estados starting → healthy / unhealthy tras fallos seguidos | El contenedor sigue Up; Compose/orquestador decide si lo recrea |
| Railway | GET al path que configuras, espera HTTP 200 | Solo durante el deploy, antes de cambiar tráfico | No activa el deployment nuevo; el anterior sigue |
| Fly.io | Checks de servicio (http_checks / tcp_checks) vs checks top-level | Servicio: rutea tráfico; top-level: monitoreo, no saca la Machine del proxy | Un check de servicio malo deja de mandarle requests |
| Caddy (VPS) | El reverse proxy reintenta upstreams caídos | En cada request si el upstream no responde | 502 al cliente hasta que el agente vuelve |
Railway lo dice claro: no monitorea el endpoint después de que el deploy quedó live. Si el agente se traba a las 3 h, Railway no se entera por el healthcheck. Ahí necesitas logs, un cron externo o un check de Fly/Docker que sí corre en loop.
Docker: instrucción mínima
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:3000/health || exit 1
Docker documenta HEALTHCHECK CMD (correr un comando) y HEALTHCHECK NONE (anular el de la imagen base). El estado empieza en starting; pasa a healthy al primer OK; a unhealthy tras N fallos seguidos. En Compose, combina esto con restart: unless-stopped — el HEALTHCHECK solo marca, no reinicia solo.
Railway: path y PORT
- El agente sirve
/health→ 200 cuando puede aceptar requests. - Service Settings → Healthcheck path:
/health. - El check usa la variable
PORTque Railway inyecta. Si tu app ignoraPORT, el check pega al puerto equivocado y el deploy nunca pasa a live.
Cero downtime de Railway = el nuevo deployment no recibe tráfico hasta el 200. No es un monitor 24/7.
Fly: no confundas los dos tipos
Los checks de servicio (services.http_checks o [[http_service.checks]]) sacan la Machine del ruteo. Los checks top-level sirven para alerta interna y no afectan el proxy. Un agente HTTP quiere el de servicio. Un worker de cola sin puerto público usa el top-level — Fly lo documenta para job runners.
Errores comunes

| Síntoma | Causa típica | Fix |
|---|---|---|
| Restart loop cada 30 s | /health llama al LLM y el proveedor falla | Check local, sin red externa |
| Deploy Railway colgado | App no escucha PORT | Bind a process.env.PORT |
| Fly manda tráfico a Machine fría | Check de servicio ausente o demasiado laxo | http_checks con grace al arranque |
Docker healthy y 502 en Caddy | Check pega a localhost interno; el proceso no escucha 0.0.0.0 | Bind correcto + el mismo /health que usa Caddy |
| “Cero downtime” y igual hay hueco | Railway no vigila post-deploy | Logs + alerta externa; no asumas monitor continuo |
Cuándo NO pongas un healthcheck “inteligente”
- El agente es un cron de Workers: el runtime ya mata el isolate; un
/healthextra no aporta. - El check necesita más de 3 s: entonces no es health, es una eval. Muévelo a observabilidad.
- Estás en Hobby de Vercel con un cron diario: el 200 del Route Handler al arrancar basta; no inventes un sidecar.
Checklist
-
/healthresponde 200 en < 50 ms, sin LLM - Liveness ≠ readiness si el agente tiene cola o BD
- Docker:
HEALTHCHECK CMDal localhost del contenedor - Railway: path +
PORT; sabes que no monitorea después - Fly: check de servicio si hay HTTP público
- El proxy (Caddy/Fly) y el HEALTHCHECK pegan al mismo bind
Siguiente paso: combina esto con observabilidad — el healthcheck dice si el proceso vive; los evals y traces dicen si el agente acierta. Si todavía no tienes runtime, el curso gratuito deja un agente local para añadirle /health antes del deploy.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



