# Web Container Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add a production frontend image so `docker build -t xqkqueue-web: ./web` succeeds and React client-side routes work behind Kubernetes Ingress. **Architecture:** A Node.js 22 Alpine build stage installs locked pnpm dependencies and creates the Vite `dist` output. An Nginx Alpine runtime stage serves that output on port 80 and falls back to `index.html` for client-side routes; Kubernetes Ingress routes `/api/*` directly to the API Service. **Tech Stack:** Node.js 22, pnpm 10.17.0, Vite 7, Nginx 1.28 Alpine, Docker ## Global Constraints - Keep the Docker build context as `./web`. - Serve the frontend on container port 80. - Do not proxy `/api` in the web container; Kubernetes Ingress owns API routing. - Use `pnpm install --frozen-lockfile` for reproducible dependency resolution. - Do not modify frontend business code or add Kubernetes manifests. --- ### Task 1: Production web image and SPA server **Files:** - Create: `web/Dockerfile` - Create: `web/nginx.conf` - Create: `web/.dockerignore` **Interfaces:** - Consumes: `web/package.json`, `web/pnpm-lock.yaml`, the Vite source tree, and a Kubernetes Ingress that sends non-API paths to port 80. - Produces: a static Nginx image exposing port 80, with Vite assets in `/usr/share/nginx/html` and SPA fallback for unknown non-file paths. - [ ] **Step 1: Reproduce the missing build definition** Run from the repository root: ```bash docker build -t xqkqueue-web:verify ./web ``` Expected before implementation: FAIL with `failed to read dockerfile: open Dockerfile: no such file or directory`. - [ ] **Step 2: Add the multi-stage Dockerfile** Create `web/Dockerfile`: ```dockerfile FROM node:22-alpine AS build WORKDIR /app RUN corepack enable && corepack prepare pnpm@10.17.0 --activate COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . RUN pnpm build FROM nginx:1.28-alpine COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` - [ ] **Step 3: Add SPA-aware Nginx configuration** Create `web/nginx.conf`: ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } } ``` - [ ] **Step 4: Exclude local and generated files from the build context** Create `web/.dockerignore`: ```dockerignore node_modules dist coverage playwright-report test-results *.log *.tsbuildinfo .DS_Store .idea .vscode ``` - [ ] **Step 5: Verify the existing production frontend build** Run: ```bash pnpm --dir web build ``` Expected: TypeScript checks and the Vite production build finish with exit code 0 and write `web/dist/index.html`. - [ ] **Step 6: Build the container image** Run: ```bash docker build --progress=plain -t xqkqueue-web:verify ./web ``` Expected: exit code 0, including successful `pnpm install --frozen-lockfile`, `pnpm build`, and Nginx runtime stages. - [ ] **Step 7: Verify root and client-side route responses** Run: ```bash docker run --rm -d --name xqkqueue-web-verify -p 18080:80 xqkqueue-web:verify curl --fail http://127.0.0.1:18080/ curl --fail http://127.0.0.1:18080/admin docker stop xqkqueue-web-verify ``` Expected: both requests return the Vite application HTML with HTTP 200, and the verification container stops cleanly. - [ ] **Step 8: Review and commit the implementation** Run: ```bash git diff --check git status --short git add web/Dockerfile web/nginx.conf web/.dockerignore git commit -m "build: add frontend container image" ``` Expected: only the three intended web container files are committed.