Build and Deploy¶
Vite Build Modes¶
| Command | import.meta.env.MODE |
import.meta.env.PROD |
API base | WS base |
|---|---|---|---|---|
pnpm dev |
development |
false |
/api (Vite proxy → :8080) |
/ws (Vite proxy → :8081) |
pnpm build:staging |
staging |
true |
https://api.ftljeta.cloud/api |
wss://ws.ftljeta.cloud |
pnpm build |
production |
true |
https://prd-api.ftljeta.cloud/api |
wss://prd-ws.ftljeta.cloud |
All URLs are hardcoded constants in ftl-frontend/src/lib/api.ts and ftl-frontend/src/hooks/useWebSocket.ts. Dead branches are tree-shaken by Vite.
Required Build Argument¶
| Arg | Where used | Effect if missing |
|---|---|---|
VITE_GOOGLE_CLIENT_ID |
Docker ARG, passed as ENV to the Vite build |
GIS is disabled; a console warning is emitted; the sign-in button does not render |
Pass it at docker build time:
Dockerfile¶
Defined at ftl-frontend/Dockerfile. Two-stage build:
Stage 1 — builder (node:20-alpine)
- Enable
pnpm@9viacorepack. - Copy
package.jsonandpnpm-lock.yaml; runpnpm install --frozen-lockfile(layer-cached separately from source). - Accept
ARG VITE_GOOGLE_CLIENT_IDand export asENVso Vite can read it. - Copy source; run
pnpm build(production mode by default).
Stage 2 — runtime (caddy:2-alpine)
- Copy
dist/from builder to/usr/share/caddy. - Copy
Caddyfileto/etc/caddy/Caddyfile. - Expose port
80. Caddy reads the Caddyfile on start.
Caddyfile¶
Defined at ftl-frontend/Caddyfile. Caddy listens on :80.
| Path | Destination | Notes |
|---|---|---|
/_frontend_health |
Inline 200 ok |
Wrapped in handle so the SPA fallback does not shadow it |
/api/* |
{env.API_UPSTREAM}:443 |
TLS dialed; Host header set to upstream FQDN; Origin stripped |
/ws/* |
{env.WS_UPSTREAM}:443 |
Same TLS setup; Caddy auto-upgrades Upgrade: websocket requests |
| Everything else | /usr/share/caddy (static) |
try_files {path} /index.html for SPA client-side routing |
Both reverse-proxy blocks add header_up X-Forwarded-Proto https. This reaches the Go api-server's ProxyHeader setting, which activates HSTS and correct cookie Secure flag behaviour behind TLS termination.
Decision: upstreams are supplied as plain hostnames (no scheme) in API_UPSTREAM / WS_UPSTREAM because Caddy's reverse_proxy does not parse URL schemes in env-var-substituted arguments. The transport http { tls } block tells Caddy to dial TLS on port 443.
Runtime Environment Variables¶
Set these in the Container App environment (not at build time):
| Variable | Example value | Purpose |
|---|---|---|
API_UPSTREAM |
api.ftljeta.cloud |
FQDN of the api-server Container App |
WS_UPSTREAM |
ws.ftljeta.cloud |
FQDN of the ws-server Container App |
Warning
Do not include https:// or a trailing slash in API_UPSTREAM / WS_UPSTREAM. Caddy appends :443 directly to the value.
Vite Dev Proxy¶
Configured in ftl-frontend/vite.config.ts:
The dev server also sets Cross-Origin-Opener-Policy: same-origin-allow-popups so Google OAuth's popup can call window.opener.postMessage back without being blocked by Chrome's cross-origin isolation policy.