# 2026-03-22 – Hosted Dev IDE via Traefik Wildcard ## Summary Long session. Explored multiple approaches to masking the raw API IP in the IDE browser URL. Ended with the correct architecture working and curl-verified. --- ## Architecture Decision Rejected: Traefik direct routing to container (removes API auth boundary, insecure) Rejected: Caddy on API host (HSTS/HTTPS issues with zerolaghub.dev domain) Chosen: Traefik on `zlh-zpack-proxy` → API → container This is correct because: - Traefik already handles TLS for the platform - API remains the auth and proxy boundary - No per-container DNS or routing side effects needed - Clean hostname with HTTPS without purchasing a cert --- ## What Was Built ### Traefik wildcard TLS - `zpackv2` certResolver already configured with Cloudflare DNS-01 challenge - Stale `_acme-challenge` TXT records in Cloudflare blocked initial cert issuance — deleted manually - Wildcard cert `*.zerolaghub.dev` issued successfully via Let's Encrypt ### Traefik dynamic config ```yaml http: routers: dev-ide: rule: "HostRegexp(`dev-{vmid:[0-9]+}.zerolaghub.dev`)" entryPoints: - websecure service: dev-ide-api tls: certResolver: zpackv2 domains: - main: "zerolaghub.dev" sans: - "*.zerolaghub.dev" services: dev-ide-api: loadBalancer: passHostHeader: true servers: - url: "http://10.60.0.245:4000" ``` `passHostHeader: true` is critical — preserves `dev-6070.zerolaghub.dev` through to the API so `handleHostedProxy` can extract the vmid. ### API devProxy.js `handleHostedProxy` added — extracts vmid from `Host` header, validates token, sets cookie, proxies to container. This was the missing piece that caused 404s until the code was deployed. `.env` aligned: - `DEV_IDE_RETURN_HOSTED_URL=true` - `DEV_IDE_HOST_SUFFIX=zerolaghub.dev` - `DEV_IDE_PUBLIC_SCHEME=https` --- ## Curl-Verified Response Chain ``` GET https://dev-6070.zerolaghub.dev/?token= → 302 + Set-Cookie: zlh_dev_ide_token GET https://dev-6070.zerolaghub.dev/ (with cookie) → 302 → /?folder=/home/dev/workspace GET https://dev-6070.zerolaghub.dev/?folder=/home/dev/workspace → 200 (code-server HTML) ``` Full chain confirmed: Browser → Traefik → API → container:6000 --- ## Key Lessons - ERR_CONNECTION_CLOSED from browser (but curl works) = H2 mismatch or wrong target. In this case it was the API not running the new code (404), not H2. - `passHostHeader: true` in Traefik is the equivalent of Caddy's `header_up Host {host}` — without it Express resolves relative redirects against the internal IP, leaking it to the browser. - Wildcard certs require DNS-01 challenge — stale TXT records in Cloudflare will block issuance silently. Check and clear them first. - Traefik and the API are on different subnets (10.70.x vs 10.60.x) — always verify cross-subnet reachability with curl before debugging proxy config. --- ## Remaining - Browser validation (curl is confirmed, browser WebSocket not yet tested) - Portal "Open IDE" button confirmation under hosted flow - Legacy `/__ide/:id` compatibility cleanup once browser is confirmed