Skip to content

build(frontend): add multi-stage Dockerfile with Next.js standalone output#87

Merged
snowrugar-beep merged 1 commit into
VertexChainLabs:mainfrom
Moonwalker-rgb:infra/frontend-dockerfile-7
Jun 24, 2026
Merged

build(frontend): add multi-stage Dockerfile with Next.js standalone output#87
snowrugar-beep merged 1 commit into
VertexChainLabs:mainfrom
Moonwalker-rgb:infra/frontend-dockerfile-7

Conversation

@Moonwalker-rgb

Copy link
Copy Markdown
Contributor

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 application issue by introducing a four-stage docker/frontend.Dockerfile (base / deps / builder / runner) that produces a minimal, non-root, health-checked runtime image using Next.js standalone output.

Closes #7

Changes

  • New docker/frontend.Dockerfile — multi-stage build on node:20-alpine:
    • base → shared Alpine layer with tini (PID 1) and libc6-compat (musl shim)
    • depsnpm ci --ignore-scripts --no-audit --no-fund for the build runner
    • buildernext build produces .next/standalone/, .next/static/, and reuses public/
    • runner → copies only the standalone bundle; USER node; ENV PORT=3000; HEALTHCHECK via wget --spider http://127.0.0.1:${PORT}/api/health; CMD ["node","server.js"]
  • New Frontend/src/app/api/health/route.ts — App Router health endpoint
    • export const dynamic = "force-dynamic" (no pre-rendering into static output)
    • returns NextResponse.json({ status: 'ok', timestamp })
  • New Frontend/.dockerignore — mirrors Backend/.dockerignore, with Next.js-specific exclusions (.next, out, next-env.d.ts, .eslintcache, build, coverage)
  • New docs sections in docker/README.md: Assets row, Frontend Targets table, Frontend design decisions, Frontend local-validation block, Backend/Frontend split under Security considerations
  • Edit Frontend/next.config.ts — enable output: 'standalone' and productionBrowserSourceMaps: false so .next/standalone/ emits a pruned, traced node_modules and client bundles don't ship inline source maps

How 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-frontend

Acceptance criteria (#7)

Criterion Status
Node.js 20 base OK ARG NODE_VERSION=20 + node:${NODE_VERSION}-alpine AS base
output: 'standalone' enabled OK Frontend/next.config.ts
Copy only standalone output OK Runner copies .next/standalone, .next/static, public only
Proper build caching OK package.json + lockfile copied before src/ + public/ + next.config.ts
Production image <100 MB Targeted; estimate 80-150 MB; measure on first CI run
Container serves on port 3000 OK ENV PORT=3000 + EXPOSE 3000
Standalone output mode enabled OK Frontend/next.config.ts
Health check configured OK wget --spider against /api/health
Non-root user OK USER node (UID 1000)
Minimal image size OK Alpine + standalone tracing + no client source maps + --ignore-scripts
Build-time deps removed in production OK Runner inherits fresh base, never builder's node_modules

Validation performed locally

  • npm install → 801 packages installed
  • npx next lint --dir src → clean for new files
  • npx tsc --noEmit → clean
  • npx vitest run → 8 test files, 44 tests pass
  • NEXT_TELEMETRY_DISABLED=1 npx next build → succeeded; .next/standalone/server.js (6.4 KB) emitted; /api/health registered as ƒ Dynamic

Checklist

  • Dockerfile created at docker/frontend.Dockerfile
  • output: 'standalone' enabled in Frontend/next.config.ts
  • Production image minimal (node:20-alpine + standalone)
  • Non-root user (USER node)
  • Health check configured (/api/health + wget --spider)
  • Locally validated (lint / typecheck / vitest / next build all green)
  • Recommended before merge: measure actual runner image size in CI and update docker/README.md
  • Documentation updated in docker/README.md
  • Commit message follows Conventional Commits format (matches pr-title-lint.yml allowed types)

Security considerations

  • Runtime user is node (UID 1000, non-root) — satisfies must not run as root
  • Runtime image contains only the standalone bundle + traced node_modules; dev tooling (eslint, vitest, typescript, husky) never enters the image
  • Source maps are not inlined into client bundles (productionBrowserSourceMaps: 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 here
  • Image is intended to be scanned by Trivy in CI; high or critical CVEs should gate the push (existing backend precedent)

Notes for reviewer

  • The branch deliberately does NOT change 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.
  • The Dockerfile uses --ignore-scripts in npm ci to skip postinstall hooks (e.g. husky prepare) since none are required for next build. Skip this flag if your shop needs those postinstalls for other reasons.
  • libc6-compat is the standard musl shim for Next.js / sharp native modules on Alpine; if your security policy prefers gcompat or a distroless base, swap in the equivalent — the inline comment in FROM base calls this out.
  • The Dockerfile is meant to be built with context: ./Frontend (mirroring the backend's context: ./Backend convention). Frontend/.dockerignore keeps the build context small and prevents secret files from being baked into layers.

Copy link
Copy Markdown
Contributor Author

Review context for PR #87

This adds a 4-stage docker/frontend.Dockerfile (Next.js standalone) that mirrors the style of docker/backend.Dockerfile (issue #6 / PR #84) so review familiarity carries over.

Closest precedent reviewers will want to compare against:

Acceptance criterion mapping (issue #7):

acceptance criterion where it lives
Node.js 20 base ARG NODE_VERSION=20 + node:20-alpine AS base in docker/frontend.Dockerfile
output: 'standalone' enabled Frontend/next.config.ts
Copy only standalone output runner stage in docker/frontend.Dockerfile (.next/standalone/, .next/static/, public/)
Proper caching package.json + lock copied before next.config.ts / src/ / public/
Container serves on port 3000 ENV PORT=3000 + EXPOSE 3000 + standalone server.js honours $PORT
Health check configured HEALTHCHECK via wget --spider http://127.0.0.1:${PORT}/api/health
Non-root user USER node (UID 1000)
Minimal image size Alpine + standalone tracing + productionBrowserSourceMaps: false + npm ci --ignore-scripts
Remove build-time deps in production Runner inherits fresh base, never builder's node_modules

Validation run locally before opening this PR:

  • npm install (801 packages)
  • npx next lint --dir src — clean for new files
  • npx tsc --noEmit — clean
  • npx vitest run — 44/44 pass
  • next build — succeeded; .next/standalone/server.js (6.4 KB) emitted; /api/health registered as ƒ Dynamic

Open follow-ups (not blocking, just FYI):

  1. Image size: 80–150 MB estimated; first CI run will give the real number.
  2. libc6-compat is sometimes flagged by Trivy — worth pinning to a specific version if your security policy requires.
  3. No vitest unit test for /api/health yet because the existing vitest.config.ts doesn't include next/server mocks — happy to add in a tiny follow-up if reviewers want.

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).

Copy link
Copy Markdown
Contributor Author

Follow-up PR #88 opened: adds vitest unit tests for the /api/health route handler (called out in this PR body as a recommended follow-up). #88

…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
@Moonwalker-rgb Moonwalker-rgb changed the title infra(frontend): add multi-stage Dockerfile with Next.js standalone output build(frontend): add multi-stage Dockerfile with Next.js standalone output Jun 23, 2026
@Moonwalker-rgb Moonwalker-rgb force-pushed the infra/frontend-dockerfile-7 branch from a2d26f9 to 6ad3e2f Compare June 23, 2026 22:21

@snowrugar-beep snowrugar-beep left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@snowrugar-beep snowrugar-beep merged commit 55ca605 into VertexChainLabs:main Jun 24, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

infra: create frontend Dockerfile for Next.js application

2 participants