From b75cfb14378114ed0c6fb5cdd78b42115c3f61aa Mon Sep 17 00:00:00 2001 From: Thorsten Date: Wed, 2 Sep 2026 16:27:23 +0200 Subject: [PATCH] Serve the frontend from docker-compose via nginx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone 9 originally planned Caddy as the reverse proxy handling both static serving and TLS. That's changed: the user has their own external nginx that will handle SSL/certs and route to this app once it's exposed beyond the LAN — so this compose stack only needs to serve the frontend over plain HTTP for now, no TLS layer of its own. Rewrote frontend/Dockerfile from the old scratch/dist-only build (meant to hand its output to Caddy) into a self-contained nginx image: builds the Vite app, then serves it from nginx on port 80. Added frontend/nginx.conf, which also reverse-proxies /api, /auth, /users, /ws, and /health to the backend container — necessary because api/client.ts calls relative paths, so the SPA and API need to appear as one origin to the browser (same pattern the Vite dev-server proxy already used, now the production equivalent). The /ws location sets the Upgrade/Connection headers for the WebSocket handshake and a long proxy_read_timeout, since DM turns can take well over nginx's 60s default before the connection sees more traffic. docker-compose.yml gets a frontend service, port 8080:80. Verified: docker compose up -d --build frontend; confirmed the built SPA loads, /api and /health proxy through to the backend (401/200, not 502), and — the part most likely to break — a real WebSocket opened successfully through the proxy and a full message round-trip (send -> persist -> broadcast -> typing -> DM turn) worked end to end against the containerized stack, not just the Vite dev server. Co-Authored-By: Claude Sonnet 5 --- docker-compose.yml | 8 +++++++ frontend/.dockerignore | 3 +++ frontend/Dockerfile | 10 +++++---- frontend/nginx.conf | 48 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 frontend/.dockerignore create mode 100644 frontend/nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml index 35f0d60..c59e0fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,5 +27,13 @@ services: ports: - "8000:8000" + frontend: + build: ./frontend + restart: unless-stopped + depends_on: + - backend + ports: + - "8080:80" + volumes: pgdata: diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..a21f178 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,3 @@ +node_modules +dist +.git diff --git a/frontend/Dockerfile b/frontend/Dockerfile index fa42836..1836728 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,11 @@ FROM node:22-slim AS build WORKDIR /app -COPY package.json . -RUN npm install +COPY package.json package-lock.json . +RUN npm ci COPY . . RUN npm run build -FROM scratch AS dist -COPY --from=build /app/dist / +FROM nginx:1.27-alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..5bc2e80 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,48 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Same-origin as far as the browser is concerned — api/client.ts calls relative paths, so + # this proxies them to the backend container internally instead of relying on CORS. + location /api/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /auth/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /users/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /health { + proxy_pass http://backend:8000; + } + + location /ws/ { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 3600s; + } + + # SPA fallback — client-side routes (e.g. /games/) must resolve to index.html. + location / { + try_files $uri /index.html; + } +}