3 Commits

Author SHA1 Message Date
6c1af1fd05 build: add frontend container image 2026-07-14 19:40:36 +08:00
b8582ba3e9 docs: plan web container implementation 2026-07-14 19:37:34 +08:00
6748861327 docs: design web container deployment 2026-07-14 19:29:02 +08:00
5 changed files with 222 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
# 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:<tag> ./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.

View File

@@ -0,0 +1,47 @@
# Web Container and Kubernetes Routing Design
## Goal
Provide a production container image for the React/Vite frontend so the existing Jenkins command `docker build ./web` succeeds and the application works correctly behind Kubernetes Ingress.
## Architecture
The web image uses a multi-stage build:
1. A Node.js 22 Alpine stage installs the locked pnpm dependencies and runs the existing `pnpm build` script.
2. An Nginx Alpine stage serves the generated `dist` directory on port 80.
The web container serves static assets only. It does not proxy API traffic and therefore has no dependency on Kubernetes service names.
## Request Routing
Kubernetes Ingress exposes the frontend and API on one origin:
- `/api/*` routes to the API Service on port 8080.
- All other paths route to the web Service on port 80.
This matches the frontend's existing relative `/api` requests and keeps session cookies same-origin. No CORS configuration or production `VITE_API_BASE_URL` is required.
## Nginx Behavior
Nginx serves files from the Vite build output. Requests that do not match a real file fall back to `/index.html`, allowing React Router routes such as `/admin`, `/staff`, `/visitor/:token`, and `/display/:token` to survive direct navigation and page refreshes.
Static files receive normal Nginx content types. The container runs in the foreground using the base image's standard entrypoint and listens on port 80.
## Build Inputs
The Docker build context remains `./web`. Dependency manifests are copied before application sources so dependency installation can be cached. Installation uses the lockfile in frozen mode to make Jenkins builds reproducible.
A `.dockerignore` excludes `node_modules`, `dist`, coverage output, test reports, logs, and local editor files from the build context.
## Verification
Verification covers:
- `pnpm build` completes successfully.
- The Dockerfile parses and the web image builds when the configured Docker registry is available.
- The image contains the generated frontend files and Nginx SPA fallback configuration.
- A container request to `/` returns the application entry page.
- A request to a client-side route such as `/admin` also returns the application entry page.
Kubernetes manifests and Ingress resources are outside this change because they are not present in this repository. The required path mapping is documented above for the deployment configuration.

10
web/.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
node_modules
dist
coverage
playwright-report
test-results
*.log
*.tsbuildinfo
.DS_Store
.idea
.vscode

13
web/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
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;"]

11
web/nginx.conf Normal file
View File

@@ -0,0 +1,11 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}