Skip to content

F4.f — Caddy full routing cutover

Migra LXC 270/271 de passthrough (proxy a VM 208 Caddy) a full routing (LXC Caddy importa managed.caddy + direct reverse_proxy a containers VM 208). Apaga Caddy VM 208 al final.

✅ Completado 2026-05-20

Cutover ejecutado con éxito. Estado actual:

  • VM 208 Caddy container: STOPPED (compose service caddy parado, no removido — recuperable con docker compose start caddy para rollback de emergencia).
  • LXC 270 / 271: full routing activo. Caddyfile apunta a managed.caddy con host:port targets (192.168.0.208:<host_port>).
  • homelab-ctl.py: refactorizado para emitir host:port targets y auto-rsync managed.caddy a LXC 270 y LXC 271 tras sync.
  • VIP .250: keepalived MASTER en 270 (.247), BACKUP en 271 (.248). Failover testeado <2s.

Tras la primera tentativa fallida del cutover (Docker DNS targets <container_name>:<port> no resoluble desde LXC porque no estaba en stacks_shared network), se refactorizó homelab-ctl.py para emitir host:port. Re-attempt OK.

Pre-cutover checklist

  • Backup completo VM 208 (PBS) verificado de hoy
  • Snapshot ZFS LXC 270 + 271 manual
  • Sablier expuesto en host port 10000 (verifica: curl http://192.168.0.208:10000/health → "OK")
  • homelab-ctl.py con SABLIER_URL=http://192.168.0.208:10000 (verifica: grep SABLIER_URL ~/scripts/homelab-ctl.py)
  • managed.caddy en ambos LXC con timestamp <5 min ago (verifica: ls -la /etc/caddy/managed.caddy en root@LXC)
  • Caddyfile.full-routing-staged validated en ambos LXC (caddy validate --config /etc/caddy/Caddyfile.full-routing-staged)
  • Ventana de mantenimiento anunciada (~5min downtime potential)

Cutover steps (10-15 min)

1. Stop Caddy VM 208 (libera ports 80/443 en host)

ssh media-208 'cd /home/monxas/stacks/reverse-proxy && docker compose stop caddy'

A partir de aquí, NADIE responde HTTPS hasta paso 2 — ventana de ~30s downtime.

2. Apply full routing en LXC primary (270)

cd ~/docs-repo/ansible
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
ansible-playbook playbook.yml --tags caddy_full_routing_apply --limit caddy-primary

Esto: - Backup /etc/caddy/CaddyfileCaddyfile.bak.passthrough - Swap Caddyfile.full-routing-stagedCaddyfile - Install new caddy.service con EnvironmentFile=/etc/caddy/.env - systemctl daemon-reload + restart caddy - Smoke test bookmarks.monxas.casa (debe ser 200/30x)

3. Validate external

for h in bookmarks grafana pocketid pics home; do
  CODE=$(curl -s --max-time 10 -o /dev/null -w "%{http_code}" https://$h.monxas.casa/)
  printf "%-30s %s\n" "$h.monxas.casa" "$CODE"
done

Si 5/5 OK, continuar paso 4. Si fail, ir a Rollback.

4. Apply LXC secondary (271)

ansible-playbook playbook.yml --tags caddy_full_routing_apply --limit caddy-secondary

5. Test failover

ssh [email protected] systemctl stop caddy
sleep 8
curl -s https://bookmarks.monxas.casa/ -o /dev/null -w "%{http_code}\n"  # debe 30x (via 271)
ssh [email protected] systemctl start caddy  # VIP regresa a 270

6. Decide on VM 208 Caddy

Opciones: - Mantener stopped indefinidamente: containers backends siguen corriendo. Caddy compose está parado pero recuperable. Recomendado primera semana. - docker compose rm caddy: borra el container. Si quieres revertir → docker compose up -d caddy. - tunnel-sync remove: ya no aplica (homelab-ctl.py sync sigue funcionando per generar managed.caddy en VM 208 + Ansible despliega a LXCs).

Rollback (si paso 3 falla)

# Restore Caddyfile passthrough en LXC 270
ssh [email protected] 'cp /etc/caddy/Caddyfile.bak.passthrough /etc/caddy/Caddyfile && systemctl restart caddy'

# Start Caddy VM 208 back
ssh media-208 'cd /home/monxas/stacks/reverse-proxy && docker compose start caddy'

# Verify
curl -s -o /dev/null -w "%{http_code}\n" https://bookmarks.monxas.casa/  # expect 307

Tiempo total rollback: ~30s + ~10s para Caddy VM 208 startup.

Post-cutover ongoing maintenance

Workflow de añadir nuevo hostname post-F4.f:

  1. Editar compose labels (tunnel.hostname=foo.monxas.casa, tunnel.port=<host_port>) → docker compose up -d.
  2. python3 ~/scripts/homelab-ctl.py sync --yes — regenera managed.caddy en VM 208 y auto-rsync a LXC 270 + LXC 271 + recarga Caddy en ambos.
  3. Listo. Fallback Ansible: ansible-playbook ~/docs-repo/ansible/playbook.yml --tags caddy_full_routing_stage --limit caddy_lxc.

Containers sin host_port

Cualquier nuevo container debe exponer puerto al host (ports: ['<host>:<container>']). Si solo tiene expose: interno, no será alcanzable desde LXC Caddy.

Gotchas conocidos

  • Sablier puerto 10000: si VM 208 cae, homelab-ctl.py sync o LXC Caddy plugin Sablier no podrá contactarlo. Las apps lazy quedan en wait page indefinida. Aceptar como tradeoff (Sablier es opcional, podría parsing a otro lugar).
  • Cert wildcard via DNS-01: primera vez LXC Caddy arranca, ACME pide cert. Tarda ~30-60s. Plan downtime adicional.
  • Smoke test del play assume bookmarks 200/30x: si Karakeep está cayendo, paso 2 da falso positivo. Reemplaza por pocketid.monxas.casa (más estable) en ansible/playbook.yml task "Wait 10s + smoke test" si quieres más confianza.
  • managed.caddy timestamp drift: si el cutover tarda >15min y haces deploy de nuevo container entre paso 1 y 2, managed.caddy en VM 208 cambia pero LXC no. Apply en paso 2 va con managed.caddy viejo. Re-stage primero si dudas.