Hosting & Nginx¶
nginx is the primary access point (port 8995 locally). It proxies API/WebSocket to uvicorn and proxies hosted app URLs directly to container ports (no Python in the hosted app path).
- FastAPI serves API endpoints and Flutter frontend static files on port 8997 (not accessed directly by users).
- Hosted app URLs (
/hosted/{workspace_id}/{port}/) are handled by an nginx regex location that extracts the port and proxies to127.0.0.1:{port}. - Subpath hosting (e.g.,
/klangk/) handled by an outer nginx that sendsX-Forwarded-Prefix,X-Forwarded-Host, andX-Forwarded-Protoheaders. Klangk's_derive_hosting_infouses these to generate correct hosted app URLs. The outer nginx also rewrites<base href>viasub_filter. - Frontend derives API URLs from
<base href>— works on both root and subpath. - WebSocket proxying via nginx
proxyWebsockets.
Topology¶
The devenv.nix runs nginx as the primary access point:
nginx reverse proxy (port 8995)
├── /hosted/{ws_id}/{port}/ → container port (direct proxy)
└── / → Klangk backend (port 8997)
In production behind a reverse proxy with subpath:
outer nginx (443)
├── /klangk/hosted/{ws_id}/{port}/ → container port (direct proxy)
└── /klangk/ → klangk nginx (port 8995)
└── / → uvicorn (port 8997)
Ports¶
KLANGK_NGINX_PORT(default8995): Primary access point — nginx serves UI, API, WebSocket, and proxies hosted app URLs directly to container portsKLANGK_PORT(default8997): Backend (FastAPI/uvicorn)9000+: User app ports (5 per workspace, mapped to container ports 8000-8004)
Tailscale and LLM Proxy¶
If the LLM provider is on a Tailscale host (e.g., a self-hosted Ollama on another machine in the tailnet), KLANGK_LLM_BASE_URL must use the Tailscale IP address, not a hostname.
The nginx LLM proxy uses lazy DNS resolution (so nginx can start even if the LLM host is temporarily unreachable). This means nginx sends raw DNS queries to the resolvers from /etc/resolv.conf. On a Tailscale host, those resolvers include MagicDNS (100.100.100.100), but MagicDNS only resolves tailnet names through the system resolver stack — raw UDP DNS queries from nginx don't go through Tailscale's networking, so both bare hostnames and FQDNs fail to resolve.
Meanwhile, KLANGK_DNS_SERVERS=100.100.100.100,8.8.8.8 is still needed for workspace containers, because podman configures container DNS with search domains that make MagicDNS work correctly inside containers.
# In .env on a Tailscale host:
KLANGK_LLM_BASE_URL=http://100.122.115.33:11434/v1 # Tailscale IP, not hostname
KLANGK_DNS_SERVERS=100.100.100.100,8.8.8.8 # for containers (works fine)
Tailscale IPs are stable and don't change, so using the IP directly is safe.