Files
Proxmox-Coolify-Manager/docs/runbooks/autostart-coolify.md
T

10 KiB

Runbook: Auto-arranque de Coolify tras apagón

Garantiza que, después de un corte de luz y al volver a encender el servidor Proxmox (192.168.0.200, nodo thinkcentre), el LXC 102 (Coolify) y su túnel de Cloudflare arranquen solos, sin intervención manual.

Causa raíz que resolvió esto (2026-07-08): el LXC 102 no tenía la marca onboot (por defecto 0), así que el host encendía pero el contenedor se quedaba apagado y, con él, todo el stack. Todo lo demás dentro del LXC ya estaba bien encadenado (Docker enabled, contenedores con restart=always / unless-stopped, cloudflared.service enabled).


Arquitectura de la solución (dos capas)

  1. Base nativa de Proxmox — onboot. El LXC 102 arranca solo al encender el host, gestionado por pve-guests.service (que ya está enabled):

    pct set 102 --onboot 1 --startup order=1,up=30
    
  2. Guardián auto-reparable — coolify-autostart.service. Un servicio systemd oneshot en el host que corre en cada arranque, después de pve-guests.service, y garantiza el stack completo:

    • /usr/local/bin/coolify-autostart.sh — script idempotente.
    • /etc/systemd/system/coolify-autostart.service — unit (WantedBy=multi-user.target).
    • Log: /var/log/coolify-autostart.log.

    En cada boot el guardián:

    1. Verifica que el LXC 102 esté running (lo arranca si no).
    2. Espera a que Docker responda dentro del LXC (deadline de reloj real, COOLIFY_MAX_WAIT, por defecto 600 s).
    3. Da un margen de asentamiento (COOLIFY_SETTLE, 180 s) para que Docker arranque sus propios contenedores, y solo entonces fuerza el arranque de los que falten: coolify-db, coolify-redis, coolify-realtime, coolify, coolify-proxy, cloudflared.
    4. Levanta el cloudflared.service de systemd dentro del LXC (segundo conector al mismo túnel).
    5. Comprueba que el túnel realmente llegó a Cloudflare: cuenta los Registered tunnel connection de este boot y los escribe en el log.

    Sale con código ≠ 0 si algo no se pudo arrancar, para que systemd lo marque failed y Restart=on-failure reintente (hasta 3 veces por hora).

    Las dos capas son complementarias: onboot hace el trabajo normal; el guardián es una red de seguridad que además auto-repara (p. ej. un contenedor con restart=no) y deja log de lo ocurrido tras el apagón.

Presupuestos de tiempo (no los bajes a ciegas)

Medido en el boot del 2026-08-07 16:41: pve-guests tarda 78 s en arrancar el CT 102, el daemon de Docker dentro del LXC solo responde ~4-5 min después del encendido, y el último contenedor core (coolify) arranca a los 7 m 40 s. Por eso:

Parámetro Valor Regla
COOLIFY_MAX_WAIT 600 s espera de Docker, por reloj real
COOLIFY_SETTLE 180 s margen antes de forzar arranques
COOLIFY_PROBE_TIMEOUT 20 s timeout duro de cada llamada al LXC
TimeoutStartSec (unit) 1200 s debe superar MAX_WAIT + SETTLE

COOLIFY_LOG también es sobreescribible, para poder hacer pruebas en seco sin tocar el log de producción.

Los archivos fuente viven en el repo en scripts/host/ y se instalan con scripts/Install-CoolifyAutostart.ps1.


Instalar / reinstalar

# Instala onboot + guardián y lo prueba una vez (idempotente y seguro)
.\scripts\Install-CoolifyAutostart.ps1 -RunNow

Opciones:

  • -VerifyOnly — solo reporta estado (onboot, unit, log). No cambia nada.
  • -SkipOnboot — instala solo el guardián, sin tocar la marca onboot.
  • -RunNow — tras instalar, dispara el guardián una vez y muestra el log.
  • -Uninstall — quita el guardián (deja onboot intacto).

El instalador empuja los archivos por SSH en base64 (normaliza CRLF→LF), inyecta el COOLIFY_LXC correcto en el unit, activa onboot, habilita el servicio y verifica.


Verificar estado (solo lectura)

.\scripts\Install-CoolifyAutostart.ps1 -VerifyOnly

Estado sano esperado:

onboot: 1
startup: order=1,up=30
enabled: enabled
state:   active
result:  success
timeout: 20min

No basta con enabled. enabled solo dice que arrancará; state/result dicen si la última ejecución funcionó. Una corrida sana del log termina en === coolify-autostart done (failures=0) ===. Si el log se corta justo después de LXC 102 already running, el guardián murió esperando a Docker.

Log del último arranque:

.\scripts\Invoke-ProxmoxSsh.ps1 -Command "tail -n 25 /var/log/coolify-autostart.log"

Probar sin apagar producción

No reinicies el host solo para probar (tumbaría todos los servicios). En su lugar, dispara el guardián manualmente — solo arranca cosas, nunca las detiene:

.\scripts\Invoke-ProxmoxSsh.ps1 -Command "systemctl start coolify-autostart.service; systemctl is-active coolify-autostart.service; tail -n 25 /var/log/coolify-autostart.log"

Para validar que el unit está bien formado y en el orden correcto:

.\scripts\Invoke-ProxmoxSsh.ps1 -Command "systemd-analyze verify /etc/systemd/system/coolify-autostart.service && systemctl show coolify-autostart.service -p After -p WantedBy"

After debe incluir pve-guests.service; WantedBy debe ser multi-user.target.

Para probar la ruta de fallo (que el guardián corte y deje ERROR en vez de colgarse), apúntalo a un LXC inexistente con un log temporal:

.\scripts\Invoke-ProxmoxSsh.ps1 -Command "env COOLIFY_LXC=999 COOLIFY_LOG=/tmp/ca-test.log COOLIFY_MAX_WAIT=15 COOLIFY_PROBE_TIMEOUT=5 bash /usr/local/bin/coolify-autostart.sh > /dev/null 2>&1; cat /tmp/ca-test.log; rm -f /tmp/ca-test.log"

Debe terminar en ERROR: docker not ready after 15s (wall clock) -> aborting en ~18 s. No toca producción ni el log real.


Incidente 2026-08-07 — el guardián llevaba 2/2 arranques muriendo

Síntoma: systemctl is-enabled decía enabled, pero la unidad estaba failed (Result: timeout) en los dos reinicios reales del día (09:47 y 16:41). El log se cortaba siempre en LXC 102 already running, sin línea de ERROR.

Cronología del boot de las 16:41:

Hora Evento
16:41:49 pve-guests arranca el CT 102
16:43:07 termina pve-guests (78 s) y arranca el guardián
16:43:08 LXC 102 already running → entra a esperar Docker
16:46:05 Docker empieza a levantar contenedores (coolify-db)
16:48:07 systemd mata al guardián: TimeoutStartSec=300
16:48:40 arranca coolify — 33 s después de que el guardián ya estaba muerto

Causa raíz (tres defectos que se sumaron):

  1. El bucle de espera contaba iteraciones de sleep, no reloj real, así que MAX_WAIT=180 no acotaba nada.
  2. Las llamadas pct exec ... docker info no tenían timeout y docker info es caro (enumera los ~50 contenedores): con el daemon saturado en el arranque en frío, una sola llamada se bloqueaba minutos y consumía todo el presupuesto en silencio.
  3. TimeoutStartSec=300 estaba por debajo del tiempo real de convergencia (~7 m 40 s), así que systemd mataba al guardián antes de que pudiera actuar.

Nunca hubo una ejecución exitosa en un arranque real: la única corrida sana del log (2026-07-08) fue el -RunNow manual con todo ya arriba.

Por qué no se notó durante un mes: -VerifyOnly solo miraba is-enabled. Ahora también reporta state, result, timeout y el journal del último boot.

Qué salvó el servicio mientras tanto: las capas base, que sí funcionaron en los dos reinicios — onboot=1, docker.service enabled, políticas restart=always/unless-stopped y cloudflared.service enabled (registró sus 4 conectores QUIC a los 3 m 41 s del boot). El guardián es red de seguridad, no el mecanismo principal; por eso el apagón no se notó de cara al usuario.

Corrección: deadline por reloj real, timeout duro en cada llamada al LXC, sonda barata docker version en vez de docker info, margen de asentamiento antes de forzar arranques, verificación de conectores del túnel en el log, TimeoutStartSec=1200 y Restart=on-failure.


Rollback

# Quitar el guardián (deja onboot como esté)
.\scripts\Install-CoolifyAutostart.ps1 -Uninstall

# Y si además quieres que el LXC deje de arrancar solo:
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct set 102 --onboot 0"

Notas

  • El guardián es idempotente: correrlo con todo ya arriba no hace nada destructivo, solo lo confirma en el log.
  • Hay dos conectores cloudflared al mismo túnel (5f0e5c5b-a180-46f2-a090-44d151006a62): el contenedor Docker cloudflared (restart=unless-stopped) y el cloudflared.service de systemd dentro del LXC (enabled). Ambos arrancan solos; es redundante pero inofensivo. Si algún día se consolida en uno, ajustar el paso 4 del script y esta nota. Ver cloudflare-tunnel.md.
  • coolify-sentinel tiene restart=no (monitor no crítico); Coolify lo recrea, por eso no está en la lista de contenedores core del guardián.
  • El único LXC con onboot es el 102. El LXC 100 hermes no tiene la marca, así que no arranca solo tras un apagón. Es intencional mientras sea secundario; si algún día deja de serlo, pct set 100 --onboot 1.

Verificado: 2026-08-07 — auditoría completa tras dos reinicios reales del día. Se detectó y corrigió el fallo del guardián (ver incidente arriba). Estado final: onboot=1, unidad enabled / active / result=success, TimeoutStartUSec=20min, Restart=on-failure, After incluye pve-guests.service, systemd-analyze verify sin warnings. Guardián ejecutado de punta a punta en 9 s con failures=0, 6 contenedores core running, cloudflared.service active con 4 conectores registrados. Ruta de aborto por reloj real probada en seco (corta a los 18 s con ERROR). Público verificado a través del túnel: coolify.urieljareth.org → 302, chat.urieljareth.org → 200.

Verificado: 2026-07-08 — instalación original (onboot=1 + guardián). La prueba de entonces fue un -RunNow manual, no un arranque real; de ahí que el defecto de tiempos no se detectara hasta 2026-08-07.