Two-stage build (uv builder + python:3.12-slim runtime) with non-root user (UID 1001), no dev deps, layer-cache-optimised dep install, and graceful SIGTERM shutdown. Verified by api/tests/build/verify_production_image.sh covering build, health endpoint, non-root, stdout logging, secret-free layers, missing-env-var exit, and dep-layer cache hit. All 102 integration tests still pass; shellcheck clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.0 KiB
Contract: Production API Container Image
This document defines the observable interface of the reactbin-api-prod container image. Any orchestration layer (Kubernetes manifests, Docker Compose, CI pipeline) MUST be written against this contract.
Network Interface
| Property | Value |
|---|---|
| Protocol | HTTP/1.1 |
| Port | 8000 (TCP) |
| Bind address | 0.0.0.0 (all interfaces inside the container) |
Health Check
The container exposes a health check at the existing API health endpoint:
GET /api/v1/health
Success response (200 OK):
{ "status": "ok" }
The container declares a built-in HEALTHCHECK with the following defaults:
| Parameter | Value |
|---|---|
| Interval | 30s |
| Timeout | 5s |
| Start period | 10s |
| Retries | 3 |
Orchestrators that define their own probes (e.g. Kubernetes livenessProbe / readinessProbe) SHOULD use this same endpoint.
Required Environment Variables
All configuration is supplied at runtime via environment variables. The image contains no defaults for secret or environment-specific values.
| Variable | Description | Example |
|---|---|---|
JWT_SECRET_KEY |
HS256 signing key for bearer tokens | change-me-long-random-string |
OWNER_USERNAME |
Username of the single owner account | owner |
OWNER_PASSWORD |
Password of the single owner account | change-me |
DATABASE_URL |
PostgreSQL connection URL (asyncpg scheme) | postgresql+asyncpg://user:pass@host:5432/db |
S3_ENDPOINT_URL |
S3-compatible object storage endpoint | https://s3.amazonaws.com |
S3_BUCKET_NAME |
Storage bucket name | reactbin-prod |
S3_ACCESS_KEY_ID |
Storage access key | AKIAIOSFODNN7EXAMPLE |
S3_SECRET_ACCESS_KEY |
Storage secret key | wJalrXUtnFEMI/K7MDENG |
S3_REGION |
Storage region | us-east-1 |
Optional environment variables (safe defaults apply):
| Variable | Default | Description |
|---|---|---|
JWT_EXPIRY_SECONDS |
86400 |
Token lifetime in seconds |
MAX_UPLOAD_BYTES |
52428800 |
Maximum upload file size (50 MB) |
LOGIN_MAX_FAILURES |
5 |
Brute-force lock threshold |
LOGIN_WINDOW_SECONDS |
300 |
Failure counting window |
LOGIN_COOLDOWN_SECONDS |
900 |
Lock duration after threshold |
LOGIN_TRUSTED_PROXY_IPS |
`` | Comma-separated trusted proxy CIDRs |
API_BASE_URL |
(not required at runtime) | Used only by client tooling |
Startup failure behaviour: If a required variable is absent, the application exits with a non-zero code before accepting any requests. The error is logged to stderr identifying the missing variable.
Signal Handling
| Signal | Behaviour |
|---|---|
SIGTERM |
Stop accepting new connections; drain in-flight requests; exit 0 within 30s |
SIGKILL |
Immediate termination (OS-level; no graceful drain possible) |
Kubernetes should configure terminationGracePeriodSeconds ≥ 30 to allow the full drain window.
Process Identity
| Property | Value |
|---|---|
| User | appuser |
| UID | 1001 |
| GID | 1001 |
| Root privileges | None |
The container MUST NOT be run with --privileged or as UID 0.
Filesystem
- Working directory:
/app - Application source:
/app/app/ - Virtual environment:
/app/.venv/ - No writable state: The container requires no persistent local storage. All state is in PostgreSQL and S3.
- Read-only root: The container is compatible with
--read-only(no writes to the filesystem at runtime).
Logging
All log output is written to stdout (info/debug) and stderr (warnings/errors). No log files are written inside the container. The container runtime log driver captures all output without additional configuration.
Image Tags
| Tag pattern | Meaning |
|---|---|
reactbin-api-prod:latest |
Latest build from master |
reactbin-api-prod:<git-sha> |
Immutable build for a specific commit |
Deployments SHOULD pin to a specific git SHA tag, not latest.