# Production images Build from the repository root (no source mounts or runtime package installs): ```sh docker build --target frontend -t curltastic-frontend . docker build --target backend -t curltastic-backend . ``` | Target | Listener | Runtime user | Healthcheck | | --- | --- | --- | --- | | `frontend` (default final target) | HTTP `8080` | `101:101` | `GET /healthz` | | `backend` | HTTP/WebSocket `3000` | `10001:10001` | `GET /` | The frontend build argument `VITE_BASE_PATH` defaults to `/curltastic/` and is passed to `vite build --base`. There are no required runtime environment variables, credentials, writable application directories, or persistent volumes. The Rust server keeps rooms in memory; rebuilding/restarting it loses games. ## Compose integration contract - Publish/route only frontend port **8080**. Explicitly set Traefik's load-balancer port to 8080; do not infer it from the upstream nginx image metadata. - Traefik routes `/curltastic/` and strips `/curltastic` before forwarding. - Attach frontend to Traefik's external `web` network and a private application network. Put backend only on that private network, with the service/DNS name **`curltastic-backend`**. Do not publish backend port 3000 or enable Traefik on it. - The browser requests public `/curltastic/ws` using its existing host, port and TLS scheme. nginx receives `/ws` and proxies the upgrade and query string to `curltastic-backend:3000/ws`. It also proxies `POST /room`, exposed publicly as `/curltastic/room`. The current frontend creates room IDs locally and does not make HTTP room requests; there is no separate frontend HTTP API URL to set. - Docker DNS is re-resolved every five seconds so backend replacement during Compose watch does not leave nginx using a stale container address. - Recommended runtime settings: `read_only: true`, `cap_drop: [ALL]`, `security_opt: [no-new-privileges:true]`. Give frontend a writable `/tmp` tmpfs (mode 1777) for nginx PID/temp files. Backend needs no writable mount. - Never mount source code, a Docker socket, or host credentials. The build context is allowlisted by `.dockerignore`; host dependencies and `.env*` are excluded. - Use Compose watch `action: rebuild`: frontend changes rebuild the `frontend` target, backend changes rebuild the `backend` target. Ignore node_modules, dist, target, .git and other generated files. Watch Dockerfile for both services and `docker/nginx.conf` for frontend. The nginx listener expects already-stripped paths; a raw host-port request to `/curltastic/` does not simulate Traefik. Test the public route through a prefix-stripping test proxy, or request `/`, `/assets/...`, `/ws`, `/room` directly when testing nginx alone. ## Development and verification Local `npm run dev` still defaults to `/` and connects to the page hostname on backend port 3000. Existing `VITE_WS_URL` overrides are preserved for custom local setups; Vite variables are build-time settings, not runtime container settings. Room navigation and share links retain the page path. ```sh (cd frontend && npm ci && npm test -- --pool=forks --poolOptions.forks.singleFork) (cd frontend && npm run build -- --base=/curltastic/) (cd backend && cargo test --locked) ``` The frontend Docker build runs its tests and TypeScript checking before producing static assets. The backend build uses the committed Cargo lockfile. Verification at implementation: frontend 26 tests passed, Rust 22 tests passed, and the production subpath frontend build passed. Both Docker build attempts were denied by the execution approval layer; image builds, nginx syntax, and live HTTP/WebSocket proxy behavior therefore remain unverified. No servers were started and no deployment was performed.