Guía9 min

Restart policy del agente IA: unless-stopped vs crash loop

Resumen

Cómo no pelearte con el kill switch: restart unless-stopped en Compose, on-failure con tope, Fly auto-restart vs scale 0. Un OOM no debe reiniciar 400 veces. Un deploy tampoco debe dejar el bot apagado para siempre. Política escrita, no el default.

Docker
Contenedor de agente que reinicia con tope frente a un crash loop

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 proceso se cae. Docker lo levanta. Se cae. Lo levanta. A las 2 AM tienes un loop de tokens y una factura. Kill switch quiere que se quede abajo. always ignora tu docker stop. Fuentes oficiales consultadas el 3 de septiembre de 2026.

La regla

Prod VPS: restart: unless-stopped (sobrevive reboot; respeta docker stop). Crash por bug: on-failure:5 (tope). Kill switch: scale 0 / stop, no pelees con always.

no (default) = un OOM y el bot queda muerto hasta que alguien mire alertas.

Compose

restart en el servicio. unless-stopped es el default sano para un webhook 24/7. always relanza incluso después de un stop consciente: el kill switch pierde. on-failure no relanza un exit 0; si tu SIGTERM sale 0, no vuelve. Combina con graceful: exit 0 = apagado limpio.

deploy.restart_policy es Swarm. En Compose clásico usa restart:.

docker run

--restart unless-stopped equivalente. --restart on-failure:5 tope. Sin política, el contenedor queda Exited.

Fly / Railway

Fly relanza Machines según health y restart en fly.toml. Un crash loop cuenta. fly scale count 0 es el stop. Railway reinicia el servicio; 0 réplicas es el off. No copies unless-stopped a wrangler: Workers no es un daemon.

OOM y CPU

Si el cgroup mata (OOM), always es un loop de muerte. Tope de restarts + alerta. Sube RAM o recorta RSS; no “más restarts”.

Tabla

Política de restart versus kill switch

PolíticaReboot hostdocker stopCrashUso
nomuertomuertomuertosolo debug
on-failure:Nmuertomuertohasta Njobs
unless-stoppedviverespeta stopvivewebhook 24/7
alwaysviverelanzavivecasi nunca

Errores comunes

Crash loop de restarts quemando la API key

SíntomaCausaFix
Bot vuelve tras killalwaysunless-stopped + scale 0
Muerto tras reboot VPSno / on-failureunless-stopped
400 arranques/horaOOM looptope + RAM
Fly flappinghealth failarregla listen (bind)
exit 0 y no vuelveon-failureunless-stopped o exit 1

Relación con el resto

Checklist

  • Compose restart: unless-stopped
  • No always en el agente con webhook
  • Tope si usas on-failure
  • Kill switch documentado vs la política
  • Alerta si flapping
  • Ensayo: docker stop queda down; reboot host vuelve

FAQ

¿systemd Restart=always? En VPS sin Compose, on-failure + StartLimitBurst. Mismo criterio.

¿K8s replicas? restartPolicy: Always en Pods es normal; el kill switch es replicas: 0.

¿Workers? No hay restart de proceso. El isolate muere por request.

Delay entre restarts

Docker no espera mucho entre crashes. Un agente que abre sqlite corrupto y muere en 50 ms martilla el disco. Pon backoff en el entrypoint o arregla la causa. Un sleep 2 eterno no es política; es parche. El tope on-failure:5 + alerta es más honesto.

Railway/Fly muestran “restart count” en el dashboard. Si sube en el día, no subas réplicas: concurrencia duplica el loop.

El ensayo: docker stop; ps vacío. Reboot del VPS (staging); el contenedor vuelve. always fallaría el primer check.

Un restart infinito no es resiliencia. Es un dosificador de tokens. Pon tope o arregla el crash.

Si el entrypoint hace while true; do node; done, Compose restart es doble. Quita el while. Un PID 1 que no muere no deja que Docker cuente fallos. El orquestador tiene que ver el exit code.

restart: unless-stopped + un volumen sqlite corrupto = loop eterno más educado. El tope no está. Por eso la alerta de flapping no es opcional: 10 restarts en 10 min es kill switch, no “ya se recupera”.

Techos oficiales (curl 3 sep 2026)

Página Start containers automatically: --restart admite no (default: no relanza), on-failure[:max-retries] (solo exit ≠ 0; no relanza si el daemon se reinicia), always (si lo paraste a mano, vuelve cuando el daemon arranca o cuando haces start), unless-stopped (como always, salvo que un stop manual o equivalente lo deja abajo incluso tras reboot del daemon). Ejemplo canónico: docker run -d --name redis --restart unless-stopped redis. En un contenedor ya corriendo: docker update --restart unless-stopped redis. Para todos los running: docker update --restart unless-stopped $(docker ps -q).

Detalles que la gente se salta:

  1. La política solo cuenta después de un start exitoso. Si el contenedor nunca llegó a arrancar, no entra en loop de restart. Eso evita un compose roto martillando el daemon.
  2. Un stop manual ignora la política hasta que el daemon se reinicia o haces start a mano. Evita el loop. unless-stopped es el que sigue ignorándola tras el reboot del daemon.
  3. Aplica a contenedores. Swarm usa otra clave (deploy.restart_policy).
  4. Foreground + restart: el CLI se va cuando el proceso sale, aunque el contenedor siga vivo por la política. El ejemplo oficial imprime 1..5, sale, tu shell vuelve; docker ps muestra el contenedor restarting con always. Un docker run sin -d no es el sitio para un webhook 24/7.

La referencia de docker container run confirma el default de --restart: no. --rm limpia el contenedor al exit: no combines --rm con una política de prod; el objeto desaparece.

--stop-timeout no es restart: segundos de espera tras --stop-signal (default SIGTERM, o STOPSIGNAL de la imagen) antes de SIGKILL. -1 = espera indefinida. Eso es graceful, no “cuántas veces vuelvo”.

on-failure:5 es el tope del daemon, no un backoff. Docker no documenta un sleep largo entre intentos. Un sqlite corrupto que muere en 50 ms sigue pegándole al disco cinco veces rápido. El tope corta el sangrado; la alerta de flapping es lo que te despierta.

Kill switch: docker stop + unless-stopped = se queda abajo en reboot del daemon. always = el reboot del daemon lo levanta otra vez. Por eso always pelea con kill switch. En Fly/Railway el equivalente de stop es réplicas 0, no un yaml de Compose.

Siguiente paso: si la política ya es unless-stopped y igual flapea, logs y OOM. Sin runtime: curso.