feat: task 1.9 gitea CI + dockerfile + nginx static serve

- Dockerfile: three-stage (deps / build / runtime). deps stage runs
  pnpm fetch with BuildKit cache mount; build stage runs vite build
  to produce dist/; runtime stage is nginx:1.27-alpine serving the
  bundle. HEALTHCHECK via wget against localhost.
- nginx.conf: gzip on text assets; /assets/ long-cache (hashed
  filenames immutable); /config.json no-cache (volume-mountable
  override in stage/prod); /index.html no-cache; SPA routing fallback
  via try_files ... /index.html.
- .dockerignore: keeps the context small (node_modules, dist, env,
  .git, .gitea, .planning, *.md except README, .claude, .vscode).
- .gitea/workflows/build.yml: matches trm/processor shape with
  format:check added between lint and test. Path filter excludes
  .planning and pure-markdown changes. Steps: checkout, Node 22,
  pnpm@latest-9, install --frozen-lockfile, typecheck, lint,
  format:check, test, buildx, registry login, build & push
  trm/spa:main, Portainer webhook.

Deviations from spec:
- Push :main tag only (not :main + per-commit SHA). Matches the
  other repos; SHA-pinning happens via *_TAG env vars in
  trm/deploy. SHA tagging is a cross-repo refactor for later.
- Pin pnpm@latest-9 (matching existing repos), not pnpm@latest
  from the spec. Reproducibility win for CI.

Smoke: typecheck/lint/format:check/build all green locally. Local
docker build not run (Docker unavailable on this machine); CI is
the gate.

Required for first deploy (1.10 covers the rest):
- REGISTRY_USERNAME / REGISTRY_PASSWORD / PORTAINER_WEBHOOK_URL
  secrets in the Gitea repo settings.
- SPA service block in trm/deploy/compose.yaml.
This commit is contained in:
2026-05-02 18:30:55 +02:00
parent 8d0bc2bb1e
commit 7e3808237e
6 changed files with 181 additions and 2 deletions
+25
View File
@@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1.7
# ---- deps stage: install with cache-friendly pnpm fetch ----
FROM node:22-alpine AS deps
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest-9 --activate
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm fetch
# ---- build stage: vite build produces dist/ ----
FROM deps AS build
COPY . .
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile --offline
RUN pnpm build
# ---- runtime: nginx serving the static bundle ----
FROM nginx:1.27-alpine AS runtime
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost/ || exit 1
# nginx default CMD is correct; don't override.