# This image's ONLY purpose is to produce /app/build — the static SPA output # (HTML/CSS/JS/manifest/service worker). There is no Node runtime in # production: per docs/PLAN.md's "Stack" and "The PWA decision", Caddy serves # these files directly and proxies /api/* to the FastAPI backend. This image # is never run as a long-lived container in production. # # Build context: this directory (apps/web), e.g. `docker build -f # apps/web/Dockerfile apps/web`. # # How deploy/ is expected to consume this: build this image, then copy its # /app/build contents out into a location the `caddy` service in # deploy/docker-compose.yml bind-mounts — either via a multi-stage # `COPY --from=bennybergle/velodrome-web: /app/build /srv/web` in the # Caddy image build, or with a one-shot `docker create` + `docker cp` / # `docker run --rm -v ...` step in the deploy pipeline that dumps /app/build # into a named volume or bind-mounted host directory before `caddy` starts. # Picked this over a scratch/busybox "artifact-holder" final stage because a # single builder stage is simpler to reason about and there's nothing here # that needs to run — the consumer only ever needs the files, not a container. # The deploy/ agent may adjust this to whatever's simplest for the compose # setup; this is just the contract (build → /app/build). FROM node:22-slim AS builder WORKDIR /app # Install dependencies first, isolated from source changes, for layer caching. COPY package.json pnpm-lock.yaml ./ RUN corepack enable && corepack prepare pnpm@9 --activate \ && pnpm install --frozen-lockfile COPY . . RUN pnpm run build # Nothing further: /app/build is the artifact. No CMD/ENTRYPOINT — this image # is not meant to be run.