Files
reactbin/specs/011-ui-prod-dockerfile/quickstart.md
agatha 1b3468b72d Feat: Add production-grade multi-stage container image for UI
Two-stage build (node:22-slim builder + nginxinc/nginx-unprivileged:alpine
runtime) with SPA fallback routing, long-lived cache headers for fingerprinted
assets, non-root user (UID 101), and no Node.js toolchain in runtime image
(82 MB vs 329 MB+ single-stage). Verified by ui/tests/build/verify_production_image.sh
covering build, health, SPA routing, non-root, stdout logging, cache-control
headers, SIGTERM exit 0, Node.js absent, secret-free layers, and dep-layer
cache hit. 102 integration tests still pass; shellcheck clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 20:18:55 +00:00

2.5 KiB
Raw Blame History

Quickstart: UI Production Image

Prerequisites

  • Docker with BuildKit enabled (default in Docker 23+)
  • make available in the shell

Build the Image

make build-ui-prod
# Equivalent: docker build -f ui/Dockerfile.prod ui/ -t reactbin-ui-prod:latest

Expected: Build completes in ~2 minutes on first run (npm install), ~15 seconds on subsequent source-only changes.

Run the Container

docker run --rm -p 4200:8080 reactbin-ui-prod:latest

Open http://localhost:4200 — the app shell loads. Navigate to /library or /tags — the page loads (SPA routing returns index.html).

Verify All Production Checks

make verify-ui-prod

This runs ui/tests/build/verify_production_image.sh, which exercises all three user stories:

[verify] Building reactbin-ui-prod:verify-<PID>...
[verify] Build OK
[verify] Polling health endpoint...
[verify] Health check passed
[verify] SPA routing OK (/library → 200)
[verify] Non-root user OK (UID <n>)
[verify] Stdout logging OK
[verify] Graceful shutdown OK (exit 0)
[verify] Node.js absent in runtime image OK
[verify] No secrets in image layers OK
[verify] Dep layer cache hit confirmed (US3 OK)
[verify] All checks passed (US1 + US2 + US3).

Integration Test Scenarios

Scenario 1: Initial Build (Cold Cache)

docker rmi reactbin-ui-prod:latest 2>/dev/null || true
make build-ui-prod

Expected: npm ci runs fully (~3090s depending on network). All packages installed from lockfile.

Scenario 2: Source-Only Rebuild (Warm Cache)

touch ui/src/app/app.component.ts
make build-ui-prod

Expected: npm ci step is CACHED (skipped). Only the Angular compilation runs (~1020s).

Scenario 3: Dependency Change (Cache Invalidation)

# Simulate a lockfile change
touch ui/package-lock.json
make build-ui-prod

Expected: npm ci runs fresh (cache miss is intentional and correct).

docker run --rm -d -p 4200:8080 --name ui-test reactbin-ui-prod:latest
curl -sf http://localhost:4200/library        # 200 + index.html
curl -sf http://localhost:4200/tags           # 200 + index.html
curl -sf http://localhost:4200/nonexistent    # 200 + index.html (Angular handles 404)
docker stop ui-test

Scenario 5: Non-Root Assertion

docker run --rm reactbin-ui-prod:latest id
# Must NOT output uid=0(root)

Scenario 6: No Node.js in Runtime Image

docker run --rm reactbin-ui-prod:latest node --version 2>&1
# Must exit non-zero (node not found)