Skip to content

Adding a new service

Cómo exponer un servicio Docker nuevo bajo *.monxas.casa con Caddy + CF Tunnel. La fuente única de configuración es homelab-ctl.py (en scripts/) que lee labels Docker, genera managed.caddy y reconfigura el ingress de CF Tunnel.

Tres archivos tocan, uno comando reconcilia

  1. docker-compose.yml del stack — labels del container.
  2. managed.caddy generado — no editar a mano.
  3. CF Tunnel ingress — no editar a mano.

Solo tocas el (1). El (2) y (3) los regenera homelab-ctl.py sync.

Workflow

1. Añadir labels al container

En el docker-compose.yml del stack en VM 208:

yaml services: newservice: image: example/newservice:latest ports: - "12345:8080" # host_port:container_port labels: - tunnel.hostname=newservice.monxas.casa - tunnel.port=12345 # Opcional: SSO PocketID (F5) - tunnel.auth=pocketid # Opcional: Sablier lazy (apaga tras 15min idle) - tunnel.sablier=true - tunnel.sablier.timeout=15m

Convenciones:

Label Valor Default
tunnel.hostname FQDN bajo monxas.casa requerido
tunnel.port Puerto en el host VM 208 requerido
tunnel.auth none | cfaccess | pocketid cfaccess
tunnel.sablier true | false false
tunnel.lan_only true excluye CF Tunnel ingress false

2. Aplicar labels al runtime

Labels solo afectan al container nuevo, así que hay que recrearlo:

bash ssh [email protected] 'cd ~/stacks/<stack> && docker compose up -d newservice'

3. Reconciliar Caddy + CF Tunnel

Desde la Mac (o el host VM 208 directamente):

bash python3 ~/scripts/homelab-ctl.py sync --yes

Esto:

  • Lee labels de TODOS los containers en VM 208.
  • Genera /etc/caddy/managed.caddy (rsync a LXC 270 y 271 post-F4).
  • Reconfigura ingress de CF Tunnel (config.yaml en LXC 123) y lo aplica.
  • Hace caddy reload en ambos Caddy HA.

4. Verificar DNS

CF tiene un wildcard *.monxas.casa desde 2026-05-20, así que no hace falta crear un registro DNS por servicio nuevo. Verifica que resuelve:

```bash dig +short newservice.monxas.casa

Debería devolver una IP de CF (1xx.xxx.xxx.xxx o 1xx anycast).

```

Internamente (LAN), Pi-hole intercepta con address=/monxas.casa/192.168.0.XXX (o .208 pre-F4) y devuelve la IP local.

5. Caso especial: Sablier activo

Si Sablier estaba activo en Caddy cuando añades el servicio, un caddy reload puede no aplicar el cambio del plugin. Workaround:

bash ssh pmx-50 'pct exec 270 -- systemctl restart caddy' ssh pmx-51 'pct exec 271 -- systemctl restart caddy'

(Restart, no reload). Esto causa una ventana de 2-3s sin servicio HTTP externo — hacerlo fuera de horario punta si posible.

6. Test

Desde dentro del cluster (bypass de DNS):

bash curl --resolve newservice.monxas.casa:443:192.168.0.XXX -fsSv https://newservice.monxas.casa/

Desde fuera:

bash curl -fsSv https://newservice.monxas.casa/

Si pides auth: comprueba que tunnel.auth esté bien (cfaccess = login Cloudflare, pocketid = login PocketID forward-auth, none = abierto).

Casos especiales

Servicio NO en VM 208

Si el backend vive en otro container o nodo (e.g. n8n en LXC 200):

yaml labels: - tunnel.hostname=n8n.monxas.casa - tunnel.upstream=http://192.168.0.XXX:5678 # No usar tunnel.port (asume VM 208 :port)

Servicio LAN-only

yaml labels: - tunnel.hostname=internal-tool.monxas.casa - tunnel.port=9999 - tunnel.lan_only=true

homelab-ctl.py configura Caddy para servirlo pero no añade ingress en CF Tunnel. Accesible solo desde LAN o WireGuard.

Servicio con WebSockets / SSE

Caddy maneja WS por defecto. Si hay timeouts SSE largos:

yaml labels: - tunnel.hostname=foo.monxas.casa - tunnel.port=1234 - tunnel.timeout=300s

Troubleshooting

Síntoma Fix
curl: ... 502 Bad Gateway Backend container muerto. docker ps | grep <service>
curl: ... 404 desde Caddy Label tunnel.hostname typo o homelab-ctl.py sync no corrió
curl: ... Could not resolve externo CF wildcard inactivo o DNS propagation. Espera 1min
curl: ... SSL_ERROR_SYSCALL interno Pi-hole devuelve IP vieja (cache). pihole -f para flush DNS
Login PocketID falla tunnel.auth=pocketid pero PocketID no tiene el client. Ver Creating OIDC client