# 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`):
```json
{ "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`.
