# 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/](../../scripts/host/) y se instalan con [scripts/Install-CoolifyAutostart.ps1](../../scripts/Install-CoolifyAutostart.ps1). --- ## Instalar / reinstalar ```powershell # 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) ```powershell .\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: ```powershell .\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: ```powershell .\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: ```powershell .\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: ```powershell .\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 ```powershell # 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](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.