# Instrucciones para Agente IA — Cloudflare Tunnel + Coolify **Entorno:** Proxmox VE → LXC CT 102 → Coolify (Docker) → Traefik + cloudflared **Dominio:** urieljareth.org **IP interna del servidor Coolify:** 192.168.0.117 --- ## HERRAMIENTAS DISPONIBLES El agente tiene acceso a: 1. **SSH a Proxmox** — para ejecutar comandos en el servidor 2. **API de Cloudflare** — para configurar DNS, Tunnel y Zero Trust sin usar la UI Credenciales necesarias antes de comenzar: - `CF_API_TOKEN` — token con permisos: `Zona → DNS → Editar` y `Zero Trust → Editar` para urieljareth.org - `CF_ACCOUNT_ID` — ID de cuenta de Cloudflare (visible en el panel principal) - `CF_ZONE_ID` — ID de zona DNS (visible en la sección DNS del dominio) - `TUNNEL_TOKEN` — token del túnel cloudflared (se obtiene o regenera via API) - Acceso SSH al host Proxmox o directamente al CT 102 (root@192.168.0.117) --- ## PASO 1 — Verificar conectividad básica desde el servidor Conectarse por SSH al contenedor de Coolify y ejecutar: ```bash # Verificar que TCP/443 saliente funciona curl -v https://cloudflare.com 2>&1 | head -10 # Verificar que UDP/7844 NO está disponible (es común en redes domésticas) nc -zv 198.41.192.37 7844 ``` **Resultado esperado:** - `curl` debe mostrar `Connected to cloudflare.com` - `nc` debe mostrar `Connection timed out` (UDP bloqueado — esto es normal y se resuelve en el Paso 3) --- ## PASO 2 — Configurar DNS en Cloudflare via API ### 2.1 Verificar registros existentes ```bash curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" | jq '.result[] | {name, type, content, proxied}' ``` ### 2.2 Crear registro CNAME wildcard (obligatorio) Reemplazar `` con el ID del túnel (formato: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`): ```bash curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "CNAME", "name": "*", "content": ".cfargotunnel.com", "proxied": true, "ttl": 1 }' ``` ### 2.3 Crear registro CNAME para coolify ```bash curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "CNAME", "name": "coolify", "content": ".cfargotunnel.com", "proxied": true, "ttl": 1 }' ``` > ⚠️ Ambos registros deben tener `proxied: true` (ícono naranja en la UI). Sin el wildcard `*`, los subdominios de las apps desplegadas devuelven `DNS_PROBE_FINISHED_NXDOMAIN`. --- ## PASO 3 — Obtener o regenerar el token del túnel ### 3.1 Listar túneles existentes ```bash curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel" \ -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {id, name, status}' ``` ### 3.2 Obtener el token del túnel existente ```bash curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel//token" \ -H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result' ``` Guardar el resultado como `TUNNEL_TOKEN`. ### 3.3 (Alternativa) Crear un túnel nuevo Solo si no existe un túnel previo: ```bash curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "name": "coolify-tunnel", "tunnel_secret": "'$(openssl rand -base64 32)'" }' | jq '{id: .result.id, name: .result.name}' ``` Luego obtener el token con el endpoint del paso 3.2. --- ## PASO 4 — Configurar las rutas del túnel via API Este es el paso más crítico. Las rutas se llaman "ingress rules" y deben configurarse en orden exacto. ### Orden obligatorio de rutas | # | Hostname | Path | Servicio | Protocolo | Notas | |---|----------|------|----------|-----------|-------| | 1 | coolify.urieljareth.org | /build/* | 192.168.0.117:8000 | http | Build logs | | 2 | coolify.urieljareth.org | /project/* | 192.168.0.117:8000 | http | **CRÍTICO: UI de apps** | | 3 | coolify.urieljareth.org | /app/* | 192.168.0.117:6001 | http | Websocket realtime | | 4 | coolify.urieljareth.org | /terminal/ws* | 192.168.0.117:6002 | http | Terminal websocket | | 5 | coolify.urieljareth.org | * | 192.168.0.117:8000 | http | Catch-all Coolify UI | | 6 | *.urieljareth.org | * | 192.168.0.117:443 | https | Apps via Traefik | ### Aplicar configuración via API ```bash curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel//configurations" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "config": { "ingress": [ { "hostname": "coolify.urieljareth.org", "path": "/build/*", "service": "http://192.168.0.117:8000" }, { "hostname": "coolify.urieljareth.org", "path": "/project/*", "service": "http://192.168.0.117:8000" }, { "hostname": "coolify.urieljareth.org", "path": "/app/*", "service": "http://192.168.0.117:6001" }, { "hostname": "coolify.urieljareth.org", "path": "/terminal/ws*", "service": "http://192.168.0.117:6002" }, { "hostname": "coolify.urieljareth.org", "service": "http://192.168.0.117:8000" }, { "hostname": "*.urieljareth.org", "service": "https://192.168.0.117:443", "originRequest": { "noTLSVerify": true } }, { "service": "http_status:404" } ] } }' ``` > ⚠️ El último elemento `http_status:404` es obligatorio. La API rechaza la configuración si no hay un catch-all final sin hostname. ### Verificar que las rutas se aplicaron correctamente ```bash curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel//configurations" \ -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result.config.ingress[] | {hostname, path, service}' ``` --- ## PASO 5 — Reglas críticas de las rutas (no negociables) ### Puertos 6001 y 6002 — siempre HTTP, nunca HTTPS Los puertos `6001` (websocket realtime) y `6002` (terminal websocket) de Coolify NO hablan TLS internamente. Si se configura `https://` para estas rutas, cloudflared devuelve: ``` tls: first record does not look like a TLS handshake ``` Siempre usar `http://192.168.0.117:6001` y `http://192.168.0.117:6002`. ### Ruta /project/* — va ANTES de /app/* Sin la regla `/project/*` en posición 2, cualquier URL con `/application/` en el path (como `/project/xxx/environment/xxx/application/xxx`) es capturada por `/app/*` y enviada al websocket del puerto 6001. La UI de Coolify carga en blanco. ### Ruta /terminal/ws* — sin barra al final Escribir `/terminal/ws*` y NO `/terminal/ws/*`. La variante con barra no hace match con las conexiones del terminal. ### Ruta *.urieljareth.org — siempre al final con noTLSVerify El wildcard debe ir después de todas las rutas específicas de `coolify.urieljareth.org`. Apunta a Traefik en puerto 443 con HTTPS y requiere `noTLSVerify: true` porque Traefik usa certificados autofirmados internamente. --- ## PASO 6 — Desplegar cloudflared en el servidor Ejecutar via SSH en el contenedor de Coolify: ```bash # Detener y eliminar el contenedor anterior si existe docker stop cloudflared 2>/dev/null docker rm cloudflared 2>/dev/null # Crear el contenedor con protocolo HTTP2 forzado docker run -d \ --name cloudflared \ --restart always \ -e TUNNEL_EDGE_PORT=443 \ cloudflare/cloudflared:latest \ tunnel --no-autoupdate --protocol http2 run --token $TUNNEL_TOKEN ``` ### Por qué --protocol http2 es obligatorio En redes domésticas, el puerto UDP/7844 que usa QUIC (el protocolo por defecto de cloudflared) está bloqueado por la mayoría de routers e ISPs. Sin `--protocol http2`, el túnel se conecta brevemente y cae en timeout cada ~5 minutos con el error: ``` failed to dial to edge with quic: timeout: no recent network activity ``` `-e TUNNEL_EDGE_PORT=443` fuerza la conexión TCP por el puerto 443, que siempre está abierto. ### Verificar que el túnel está activo ```bash docker logs cloudflared --tail 20 ``` Resultado correcto — deben aparecer 4 líneas como esta: ``` INF Registered tunnel connection connIndex=0 ... protocol=http2 INF Registered tunnel connection connIndex=1 ... protocol=http2 INF Registered tunnel connection connIndex=2 ... protocol=http2 INF Registered tunnel connection connIndex=3 ... protocol=http2 ``` Si aparece `Provided Tunnel token is not valid`, el token se truncó. Repetir el Paso 3.2 para obtenerlo completo. --- ## PASO 7 — Configurar Traefik con DNS Challenge Esto elimina la dependencia de validación HTTP para obtener certificados SSL. Es necesario cuando "Always Use HTTPS" está activo en Cloudflare (que redirige las validaciones HTTP de Let's Encrypt antes de que lleguen a Traefik). ### 7.1 Crear token de API para DNS Challenge El token debe tener permisos: `Zona → DNS → Editar` para `urieljareth.org`. ```bash # Verificar que el token tiene los permisos correctos curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer $CF_DNS_API_TOKEN" | jq '{status: .result.status}' ``` ### 7.2 Editar el docker-compose de Traefik via SSH ```bash # Hacer backup primero cp /data/coolify/proxy/docker-compose.yml /data/coolify/proxy/docker-compose.yml.bak # Editar nano /data/coolify/proxy/docker-compose.yml ``` Agregar dentro del servicio `traefik`, sección `environment`: ```yaml environment: - CF_DNS_API_TOKEN= ``` Reemplazar las líneas de `httpchallenge` en `command` por: ```yaml - '--certificatesresolvers.letsencrypt.acme.dnschallenge=true' - '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare' - '--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53' - '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json' ``` ### 7.3 Limpiar certificados anteriores y reiniciar ```bash echo '{}' > /data/coolify/proxy/acme.json chmod 600 /data/coolify/proxy/acme.json docker compose -f /data/coolify/proxy/docker-compose.yml up -d --force-recreate ``` > ⚠️ Si hay error 429 en los logs de coolify-proxy, Let's Encrypt aplicó rate limit por intentos fallidos previos. Esperar 1 hora antes de reintentar. --- ## PASO 8 — Verificación final ### Verificar estado de todos los servicios ```bash # Contenedores corriendo docker ps --format "table {{.Names}}\t{{.Status}}" | grep -E "coolify|cloudflared|traefik" # Túnel con 4 conexiones activas docker logs cloudflared --tail 5 | grep "Registered tunnel" # Traefik sin errores de certificado docker logs coolify-proxy --tail 50 2>&1 | grep -i "error\|certificate\|acme\|429" ``` ### Verificar rutas desde afuera ```bash # Coolify UI debe responder 200 curl -s -o /dev/null -w "%{http_code}" https://coolify.urieljareth.org # Una app desplegada debe responder 200 o 301 curl -s -o /dev/null -w "%{http_code}" https://miapp.urieljareth.org ``` --- ## DIAGNÓSTICO DE ERRORES COMUNES | Error | Causa más probable | Verificar | |-------|--------------------|-----------| | `530` — Unregistered from Argo Tunnel | cloudflared caído | `docker logs cloudflared --tail 20` | | `502 Bad Gateway` | Traefik sin cert SSL o puerto 3000 | Logs de coolify-proxy | | Página en blanco al abrir app en Coolify | Falta regla `/project/*` | Verificar rutas del túnel | | `tls: first record does not look like TLS` | https:// en puerto 6001 o 6002 | Corregir a http:// en ingress rules | | `DNS_PROBE_FINISHED_NXDOMAIN` | Falta CNAME wildcard | Verificar registro `*` en DNS | | Túnel cae cada 5 min | UDP/7844 bloqueado | Confirmar `--protocol http2` en el contenedor | | `429` en logs de Traefik | Rate limit de Let's Encrypt | Esperar 1h, luego reiniciar Traefik | | `Provided Tunnel token is not valid` | Token truncado | Obtener token completo via API (Paso 3.2) | --- ## FLUJO PARA DESPLEGAR UNA APP NUEVA Por un bug en Coolify beta.472, la UI de apps individuales no carga tras la creación. Usar este flujo: ```bash # 1. Obtener el ID y UUID de la app recién creada docker exec coolify-db psql -U coolify -d coolify \ -c "SELECT id, uuid, name, fqdn FROM applications ORDER BY id DESC LIMIT 3;" # 2. Actualizar dominio y puerto antes del deploy docker exec coolify-db psql -U coolify -d coolify \ -c "UPDATE applications SET fqdn='https://miapp.urieljareth.org', ports_exposes='80' WHERE id=;" # 3. Hacer deploy via API de Coolify curl -X POST "http://localhost:8000/api/v1/deploy?uuid=&force=false" \ -H "Authorization: Bearer " # 4. Verificar que Traefik apunta al puerto correcto grep "server.port" /data/coolify/applications//docker-compose.yaml ``` Si el puerto sigue siendo 3000 en lugar de 80: ```bash sed -i 's/loadbalancer.server.port=3000/loadbalancer.server.port=80/g' \ /data/coolify/applications//docker-compose.yaml docker compose -f /data/coolify/applications//docker-compose.yaml up -d --force-recreate ``` --- ## ARCHIVOS DE REFERENCIA EN EL SERVIDOR | Archivo | Ruta | |---------|------| | Docker-compose de Traefik | `/data/coolify/proxy/docker-compose.yml` | | Backup de Traefik | `/data/coolify/proxy/docker-compose.yml.bak` | | Certificados SSL | `/data/coolify/proxy/acme.json` | | Docker-compose de cada app | `/data/coolify/applications//docker-compose.yaml` |