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