# The Other Dude — Traefik dynamic configuration example # # This config assumes: # - TOD frontend runs on FRONTEND_HOST:3000 # - TOD API runs on API_HOST:8001 # - WinBox worker Xpra ports are on WORKER_HOST:10100-10119 # - Traefik entrypoints: web (80) and websecure (443) # # Replace tod.example.com and upstream addresses with your values. # # For Docker-based Traefik, labels can replace this file. # This example uses file provider for clarity. http: routers: # ── Xpra (Remote WinBox) ──────────────────────────────────────── # Must be higher priority than the frontend catch-all. # Each Xpra port needs its own service since Traefik doesn't # support dynamic port extraction from path regex. # Shown for port 10100; duplicate for 10101-10119 as needed. tod-xpra-10100: rule: "Host(`tod.example.com`) && PathPrefix(`/xpra/10100/`)" entryPoints: [websecure] service: tod-xpra-10100 middlewares: [xpra-strip, xpra-headers] tls: certResolver: letsencrypt priority: 30 # ── API ───────────────────────────────────────────────────────── tod-api: rule: "Host(`tod.example.com`) && PathPrefix(`/api/`)" entryPoints: [websecure] service: tod-api middlewares: [security-headers] tls: certResolver: letsencrypt priority: 20 # ── Frontend (SPA) ────────────────────────────────────────────── tod-frontend: rule: "Host(`tod.example.com`)" entryPoints: [websecure] service: tod-frontend middlewares: [security-headers] tls: certResolver: letsencrypt priority: 10 services: tod-xpra-10100: loadBalancer: servers: - url: "http://YOUR_TOD_HOST:10100" # Add tod-xpra-10101 through tod-xpra-10119 as needed tod-api: loadBalancer: servers: - url: "http://YOUR_TOD_HOST:8001" tod-frontend: loadBalancer: servers: - url: "http://YOUR_TOD_HOST:3000" middlewares: xpra-strip: # Strip /xpra/{port} prefix before forwarding stripPrefixRegex: regex: ["^/xpra/[0-9]+"] xpra-headers: headers: # Relaxed CSP for Xpra HTML5 client (inline scripts + eval) customResponseHeaders: Content-Security-Policy: "default-src 'self' 'unsafe-inline' 'unsafe-eval' ws: wss: data: blob:; frame-ancestors 'self';" X-Content-Type-Options: "nosniff" # IMPORTANT: Disable compression for Xpra — compressing WebSocket # binary frames corrupts mouse/keyboard coordinate data. security-headers: headers: frameDeny: true contentTypeNosniff: true browserXssFilter: true referrerPolicy: "strict-origin-when-cross-origin" customResponseHeaders: X-Frame-Options: "SAMEORIGIN" # IMPORTANT: Disable Traefik's built-in compression for Xpra routes. # If using --entrypoints.websecure.http.middlewares=compress@..., # exclude the xpra router or WebSocket binary frames will be corrupted.