Guía9 min

stop_grace_period: el agente termina el webhook antes del SIGKILL

Resumen

Cómo no cortar un tool call a los 10s: stop_grace_period en Compose, docker stop -t, distinto del handler SIGTERM. Default Linux 10s / Windows 30s. Fly 5s max 300s. Railway 0s de drain. Techos oficiales curl 3 de septiembre de 2026.

Docker
Línea de tiempo SIGTERM a SIGKILL con margen para drenar el webhook

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 stop manda SIGTERM y, por defecto 10s (spec Compose), SIGKILL. Un agente con tool de 20s muere a media respuesta. stop_grace_period: 30s (o lo que mida tu handler). Distinto de graceful shutdown: esa guía es el código; esta es el techo de Compose. Fuentes oficiales consultadas el 3 de septiembre de 2026.

La regla

stop_grace_period: 30s

en el servicio agent. Postgres puede 10s. Caddy 10s. El bot no.

El spec (curl 2026-09-03): stop_grace_period es cuánto espera Compose si el contenedor no maneja SIGTERM (o el stop_signal que hayas puesto) antes de SIGKILL. Duración: ejemplos oficiales 1s y 1m30s. Default: 10 segundos.

docker stop -t 30 es el mismo knob fuera de Compose. Fly/Railway tienen su propio drain; no copies yaml ahí.

Relación con SIGTERM

Sin handler, 30s de espera = 30s idle + KILL. El código debe cerrar el server HTTP y no aceptar webhooks nuevos. init/tini reenvía señales; sin PID 1 correcto el grace no llega.

restart no alarga el stop.

Techos oficiales (CLI vs yaml)

docker container stop (docs CLI, curl 2026-09-03):

  • El proceso principal recibe SIGTERM; tras el grace, SIGKILL.
  • La primera señal se cambia con STOPSIGNAL en el Dockerfile o --stop-signal en run/create.
  • -t / --timeout: segundos a esperar. Si no sale, SIGKILL.
  • --timeout -1: sin timeout; el daemon espera indefinido. No lo pongas en CI.
  • Default si no hay --stop-timeout al crear: el daemon usa 10 s Linux y 30 s Windows.

Compose spec stop_signal: si no lo pones, Compose manda SIGTERM. Ejemplo oficial: SIGUSR1. No inventes SIGUSR2 “porque Node”.

docker compose stop y docker compose down exponen -t / --timeout (“shutdown timeout in seconds”). El flag de CLI pisa el yaml. Scripts con compose down --timeout 2 matan el bot aunque el yaml diga 30s.

compose down (docs): para contenedores, redes del bloque networks y la default. Redes/volumes external no se borran. Eso no cambia el grace; sí cambia qué desaparece cuando el timeout gana.

Fly / Railway / serverless

Fly fly.toml (curl 2026-09-03):

  • Señal por defecto al apagar una Machine: SIGINT, no SIGTERM. En Node, SIGINT suele ser “Ctrl+C duro”. Pon kill_signal = "SIGTERM" si tu handler es el de TERM.
  • kill_timeout: espera tras la señal. Default 5 s. Máximo 300 s (5 min). Docs: best-effort; el app debe tolerar cortes más cortos.
  • Orden: señal al proceso de ENTRYPOINT/CMD → espera kill_timeout → shutdown forzado.

Railway (reference/deployments, curl 2026-09-03): cuando el deploy nuevo está online, el viejo recibe SIGTERM. Default: 0 segundos de drain y luego SIGKILL. No mandan otras señales. El margen es RAILWAY_DEPLOYMENT_DRAINING_SECONDS. 0s + tool de 20s = corte seguro. No copies stop_grace_period al service de Railway.

Workers: isolates; no hay SIGTERM de contenedor. Vercel Functions: maxDuration, no Compose.

Tabla

Margen SIGTERM versus KILL

Servicio / knobGracePor quéTecho verificado
agent (Compose)30–60stools + webhook in-flightspec duración; default 10s
postgres10scheckpoint cortodefault Compose
caddy10sdrain HTTPdefault Compose
migrate (profile)5sone-shotno copies 60s
docker stop Linux10s defaultdaemonCLI stop
docker stop Windows30s defaultdaemonCLI stop
docker stop -t -1infinitono en CICLI stop
Fly5s default, max 300skill_timeoutfly.toml
Fly señal defaultSIGINTno TERMfly.toml
Railway0s defaultRAILWAY_DEPLOYMENT_DRAINING_SECONDSdocs deploy
exit 137128+9 SIGKILLgrace corto o sin handlerUnix + CLI SIGKILL

Errores comunes

SIGKILL a los 10s

SíntomaCausaFix
exit 137 en deploydefault 10sstop_grace_period
deploy “cuelga” 5 min300s absurdo / Fly maxmide p95 tool
SIGTERM no llegaPID 1 Nodetini
Fly yaml gracecopy Composekill_timeout + kill_signal
stop -t 1 en scriptsoverride CLIalinea con yaml
Railway corte a 0sdefault drainRAILWAY_DEPLOYMENT_DRAINING_SECONDS
Fly mata “ya”default SIGINTkill_signal = "SIGTERM"
--timeout -1 en CIespera infinitanúmero finito

Relación con el resto

Checklist

  • stop_grace_period en agent ≥ p95 tool + 5s (cap 60s salvo batch)
  • Handler SIGTERM real
  • tini / init: true
  • Scripts stop -t / down --timeout alineados al yaml
  • Ensayo: compose stop agent sin 137
  • Postgres no espera 60s inútil
  • Fly: kill_signal TERM + kill_timeout medido (≤300)
  • Railway: drain > 0 si hay tools in-flight

FAQ

¿Workers? Isolates; no hay SIGTERM de contenedor.

¿Vercel? Timeout de function, no Compose.

¿30s vs 60s? Mide. 60s atrasa deploys. Fly te deja hasta 300s; no es una invitación.

El ensayo: webhook lento (sleep 15). compose stop; el handler termina; exit 0. Sin grace: 137.

No pongas 10m “por si acaso”. El orquestador espera. Techo: p99 + margen. Spec admite 1m30s; el problema no es la sintaxis, es el rolling.

Qué no hacer

No subas grace y dejes server.close() sin implementar. No uses stop_signal: SIGKILL “para que pare ya”: pierdes el drain. No copies 30s a postgres: alargas compose down en cada deploy.

Scripts de CI con docker compose down --timeout 2 pisan el yaml. El flag gana. Alinea --timeout al mismo número.

Deploy rolling

compose up -d recrea: stop del viejo + start del nuevo. Si grace es 60s, el rolling es 60s+health. depends_on healthy espera postgres, no el drain del agente.

El ensayo extra: time compose stop agent ≈ tu grace si el handler se cuelga; si el handler es correcto, mucho menos.

stop_signal

Default SIGTERM (spec). stop_signal: SIGINT solo si tu proceso ya lo trata (Ctrl+C local). Un solo contrato: TERM → close → exit 0.

Dockerfile STOPSIGNAL y docker run --stop-signal son el mismo canal que el CLI documenta para docker stop. En Compose, el campo del servicio es el que gana para compose stop.

Si usas un wrapper shell sin exec, el signal no llega: otra vez tini o exec node.

Telegram reintenta webhooks si cortas con 5xx. SIGKILL = timeout del cliente + retry. Drain bien = 200 y no doble tool.

Qué medir

p50/p95 de duración de tools en logs. Grace = p95 + 5–10s, cap 60s salvo jobs batch (esos van profile aparte, no el webhook).

Siguiente paso: si ya hay 30s y igual 137, el proceso no ve SIGTERM → tini. Sin runtime: curso.