zlh-grind/Session_Summaries/2026-03-22_Hosted-IDE-Traefik-Wildcard.md

3.2 KiB
Raw Permalink Blame History

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

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=<valid>
  → 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