Compare commits
4 Commits
codex/proj
...
6c1af1fd05
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c1af1fd05 | |||
| b8582ba3e9 | |||
| 6748861327 | |||
| 4a73f81195 |
141
docs/superpowers/plans/2026-07-14-web-container.md
Normal file
141
docs/superpowers/plans/2026-07-14-web-container.md
Normal 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.
|
||||||
47
docs/superpowers/specs/2026-07-14-web-container-design.md
Normal file
47
docs/superpowers/specs/2026-07-14-web-container-design.md
Normal 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.
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
FROM golang:1.26.3-alpine AS build
|
FROM golang:1.26.3-alpine AS build
|
||||||
|
ARG GOPROXY=https://goproxy.cn,direct
|
||||||
|
ENV GOPROXY=${GOPROXY}
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY go.mod go.sum* ./
|
COPY go.mod go.sum* ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|||||||
10
web/.dockerignore
Normal file
10
web/.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
playwright-report
|
||||||
|
test-results
|
||||||
|
*.log
|
||||||
|
*.tsbuildinfo
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
13
web/Dockerfile
Normal file
13
web/Dockerfile
Normal 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
11
web/nginx.conf
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user