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>
91 lines
4.1 KiB
Markdown
91 lines
4.1 KiB
Markdown
# 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.
|