build(frontend): add multi-stage Dockerfile with Next.js standalone output#87
Merged
snowrugar-beep merged 1 commit intoJun 24, 2026
Conversation
Contributor
Author
Review context for PR #87This adds a 4-stage Closest precedent reviewers will want to compare against:
Acceptance criterion mapping (issue #7):
Validation run locally before opening this PR:
Open follow-ups (not blocking, just FYI):
cc @gbengaeben (wrote the backend Dockerfile — strongest reviewer for pattern consistency) and @BigJohn-dev (CI workflows author — for the README's "Trivy in CI" claim). |
Contributor
Author
…utput Closes VertexChainLabs#7 Replaces the rejected type-prefix infra(frontend): (not in the .github/workflows/pr-title-lint.yml allowlist) with build(frontend):, the Conventional Commits spec type for changes to the build system. The diff is unchanged; only the commit message is rewritten. Adds a production-ready container build for the Next.js frontend: - Add docker/frontend.Dockerfile (multi-stage base/deps/builder/runner on node:20-alpine, libc6-compat + tini, non-root user, /api/health healthcheck, port 3000, CMD [node,server.js]) - Enable output: 'standalone' and productionBrowserSourceMaps: false in Frontend/next.config.ts so .next/standalone/ emits a pruned, traced node_modules - Add Frontend/src/app/api/health/route.ts (NextResponse.json, dynamic = force-dynamic) - Add Frontend/.dockerignore mirroring Backend/.dockerignore (Next.js specifics: .next, out, next-env.d.ts, eslintcache) - Document new file, Targets table, design decisions, and validation in docker/README.md
a2d26f9 to
6ad3e2f
Compare
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a production-ready container build for the Next.js frontend that resolves issue #7.
Implements the
infra: create frontend Dockerfile for Next.js applicationissue by introducing a four-stagedocker/frontend.Dockerfile(base/deps/builder/runner) that produces a minimal, non-root, health-checked runtime image using Next.js standalone output.Closes #7
Changes
docker/frontend.Dockerfile— multi-stage build onnode:20-alpine:base→ shared Alpine layer withtini(PID 1) andlibc6-compat(musl shim)deps→npm ci --ignore-scripts --no-audit --no-fundfor the build runnerbuilder→next buildproduces.next/standalone/,.next/static/, and reusespublic/runner→ copies only the standalone bundle;USER node;ENV PORT=3000;HEALTHCHECKviawget --spider http://127.0.0.1:${PORT}/api/health;CMD ["node","server.js"]Frontend/src/app/api/health/route.ts— App Router health endpointexport const dynamic = "force-dynamic"(no pre-rendering into static output)NextResponse.json({ status: 'ok', timestamp })Frontend/.dockerignore— mirrorsBackend/.dockerignore, with Next.js-specific exclusions (.next,out,next-env.d.ts,.eslintcache,build,coverage)docker/README.md: Assets row, Frontend Targets table, Frontend design decisions, Frontend local-validation block, Backend/Frontend split under Security considerationsFrontend/next.config.ts— enableoutput: 'standalone'andproductionBrowserSourceMaps: falseso.next/standalone/emits a pruned, tracednode_modulesand client bundles don't ship inline source mapsHow to test
docker build --target runner -f docker/frontend.Dockerfile ./Frontend docker run --rm -p 3000:3000 --name vertex-frontend \ $(docker build -q --target runner -f docker/frontend.Dockerfile ./Frontend) sleep 10 curl -fsS http://localhost:3000/api/health # -> {"status":"ok","timestamp":"..."} curl -fsS http://localhost:3000/ # -> landing page HTML docker inspect --format='{{json .State.Health.Status}}' vertex-frontend # -> "healthy" docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep vertex-frontendAcceptance criteria (#7)
ARG NODE_VERSION=20+node:${NODE_VERSION}-alpine AS baseoutput: 'standalone'enabledFrontend/next.config.ts.next/standalone,.next/static,publiconlypackage.json+ lockfile copied beforesrc/+public/+next.config.tsENV PORT=3000+EXPOSE 3000Frontend/next.config.tswget --spideragainst/api/healthUSER node(UID 1000)--ignore-scriptsbase, neverbuilder'snode_modulesValidation performed locally
npm install→ 801 packages installednpx next lint --dir src→ clean for new filesnpx tsc --noEmit→ cleannpx vitest run→ 8 test files, 44 tests passNEXT_TELEMETRY_DISABLED=1 npx next build→ succeeded;.next/standalone/server.js(6.4 KB) emitted;/api/healthregistered asƒ DynamicChecklist
docker/frontend.Dockerfileoutput: 'standalone'enabled inFrontend/next.config.tsnode:20-alpine+ standalone)USER node)/api/health+wget --spider)next buildall green)runnerimage size in CI and updatedocker/README.mddocker/README.mdpr-title-lint.ymlallowed types)Security considerations
node(UID 1000, non-root) — satisfiesmust not run as rootnode_modules; dev tooling (eslint, vitest, typescript, husky) never enters the imageproductionBrowserSourceMaps: false) — an attacker pulling the image cannot reconstruct original source from.next/static/NEXT_PUBLIC_*env vars are intentionally baked in (framework contract for browser-visible env vars); secrets that must remain server-only belong on the backend image, not hereNotes for reviewer
Frontend/package-lock.json. Any incidental lockfile drift from local validation was reverted before commit so the PR diff is strictly issue-infra: create frontend Dockerfile for Next.js application #7 work.--ignore-scriptsinnpm cito skip postinstall hooks (e.g.husky prepare) since none are required fornext build. Skip this flag if your shop needs those postinstalls for other reasons.libc6-compatis the standard musl shim for Next.js / sharp native modules on Alpine; if your security policy prefersgcompator a distroless base, swap in the equivalent — the inline comment inFROM basecalls this out.context: ./Frontend(mirroring the backend'scontext: ./Backendconvention).Frontend/.dockerignorekeeps the build context small and prevents secret files from being baked into layers.