Files
reactbin/specs/011-ui-prod-dockerfile/contracts/container.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

4.1 KiB

Container Interface Contract: UI Production Image

Image Identity

Property Value
Image name reactbin-ui-prod
Runtime nginx-unprivileged (Alpine)
Listen port 8080
Run user non-root (UID ≠ 0)

Runtime Inputs

Environment Variables

The UI container is a static file server. It has no required environment variables at runtime — all configuration is compiled into the static assets at build time by the Angular build toolchain.

Note: The API base URL is baked in at build time via Angular's environment configuration. A future iteration may introduce runtime environment injection via a served config.json, but this is out of scope for v1.

Runtime Outputs

HTTP Interface

Route pattern Behaviour
/ Returns index.html with HTTP 200
/ (any SPA path) Returns index.html with HTTP 200 (SPA fallback via try_files)
/main.*.js Returns fingerprinted JS bundle with long-lived cache headers
/styles.*.css Returns fingerprinted CSS with long-lived cache headers
/assets/* Returns static assets
Any path not found Returns index.html with HTTP 200 (Angular router handles 404)

Cache Headers

Asset type Cache-Control header
Fingerprinted bundles (.js, .css, fonts) public, max-age=31536000, immutable
index.html no-store, no-cache, must-revalidate

Process Exit

Signal Expected exit code Maximum wait
SIGTERM 0 30 seconds
SIGKILL non-zero immediate

Health Check

Property Value
Command wget -qO- http://localhost:8080/
Interval 30 seconds
Timeout 5 seconds
Start period 15 seconds
Retries 3

The health check passes when nginx responds with any 2xx status on the root path.

Image Constraints

Constraint Requirement
Node.js runtime present MUST NOT be present in runtime image
node_modules/ present MUST NOT be present in runtime image
Source TypeScript files MUST NOT be present in runtime image
Secrets in layer history MUST NOT appear in any docker history layer
Run as root MUST NOT — process UID MUST be non-zero

Build Interface

Property Value
Dockerfile path ui/Dockerfile.prod
Build context ui/ directory
Build command docker build -f ui/Dockerfile.prod ui/ -t reactbin-ui-prod:latest

Build Context Exclusions (.dockerignore)

The following MUST be excluded from the build context to keep transfers fast and avoid leaking dev state:

  • node_modules/ — always rebuilt via npm ci in the build stage
  • dist/ — always rebuilt; must not pollute the build stage
  • .git/ — not needed for build
  • *.spec.ts — test files not compiled into production output
  • .env* — dev environment files
  • src/**/*.spec.ts — test specs

Verification

The contract is verified end-to-end by ui/tests/build/verify_production_image.sh. Running make verify-ui-prod MUST pass all contract checks.