# Quickstart: Production API Container Image ## Prerequisites - Docker 24+ installed and running on the host - `make` available - A copy of `.env` (or the env vars from `.env.example`) for smoke-testing --- ## Build the Production Image ```sh make build-prod # Equivalent: docker build -f api/Dockerfile.prod api/ -t reactbin-api-prod:latest ``` On a warm cache (deps unchanged), the build should complete in under 60 seconds because the dependency layer is reused. --- ## Verify the Production Image (TDD Smoke Test) ```sh make verify-prod ``` This runs `api/tests/build/verify_production_image.sh`, which: 1. Builds the image (fails fast if `Dockerfile.prod` is missing — the **red** TDD state) 2. Starts the container with test env vars 3. Polls `/api/v1/health` until it returns 200 (or times out after 30s) 4. Asserts the API process is running as a non-root user (UID ≠ 0) 5. Sends SIGTERM and asserts the container exits with code 0 within 30s 6. Asserts `pytest` is NOT importable inside the container (dev deps excluded) **Expected output (green)**: ``` [verify] Building reactbin-api-prod:test ... [verify] Build OK [verify] Starting container ... [verify] Health check passed (GET /api/v1/health → 200) [verify] Process user: 1001 (non-root ✓) [verify] Sending SIGTERM ... [verify] Container exited with code 0 (graceful shutdown ✓) [verify] Dev deps absent ✓ [verify] All checks passed. ``` --- ## User Story Integration Scenarios ### US1 — API Runs Reliably in Production ```sh # Start container with real (or test) env vars docker run --rm -d \ --name reactbin-test \ -p 8000:8000 \ -e JWT_SECRET_KEY=my-secret \ -e OWNER_USERNAME=owner \ -e OWNER_PASSWORD=changeme \ -e DATABASE_URL=postgresql+asyncpg://user:pass@host:5432/db \ -e S3_ENDPOINT_URL=http://minio:9000 \ -e S3_BUCKET_NAME=reactbin \ -e S3_ACCESS_KEY_ID=minioadmin \ -e S3_SECRET_ACCESS_KEY=minioadmin \ -e S3_REGION=us-east-1 \ reactbin-api-prod:latest # Check health curl http://localhost:8000/api/v1/health # → {"status":"ok"} # Graceful shutdown docker stop reactbin-test # sends SIGTERM docker wait reactbin-test # → exit code 0 ``` ### US2 — Minimal, Secure Container ```sh # Verify non-root user docker inspect --format='{{.Config.User}}' reactbin-api-prod:latest # → appuser (or 1001) # Verify no dev packages (pytest should not be importable) docker run --rm reactbin-api-prod:latest \ /app/.venv/bin/python -c "import pytest" 2>&1 # → ModuleNotFoundError: No module named 'pytest' # Verify no source control or test files in image docker run --rm reactbin-api-prod:latest ls /app # → app .venv (no tests/, no alembic/, no .git/) ``` ### US3 — Fast, Reproducible Builds ```sh # First build (cold): installs all deps time docker build --no-cache -f api/Dockerfile.prod api/ -t reactbin-api-prod:cold # Touch a source file only (no dep change) touch api/app/main.py # Second build: dependency layer served from cache time docker build -f api/Dockerfile.prod api/ -t reactbin-api-prod:warm # Expect: warm build < 30s; cold build varies (network-dependent) # Confirm same health response from both docker run --rm ... reactbin-api-prod:cold docker run --rm ... reactbin-api-prod:warm ``` --- ## Missing Env Var Behaviour ```sh docker run --rm \ -e JWT_SECRET_KEY=my-secret \ # OWNER_USERNAME intentionally omitted reactbin-api-prod:latest # → Container exits non-zero, stderr logs: "field required: owner_username" ``` --- ## Read-Only Filesystem Compatibility ```sh docker run --rm --read-only \ -e JWT_SECRET_KEY=... [other env vars] \ reactbin-api-prod:latest & curl http://localhost:8000/api/v1/health # → {"status":"ok"} ```