Files
kite/.planning/phases/06-performance-production-hardening/06-04-SUMMARY.md
T

3.2 KiB

plan, phase, status, completed_at, commits, checkpoint_pending
plan phase status completed_at commits checkpoint_pending
06-04 06-performance-production-hardening partial 2026-06-04
3788226
task-3-human-verify

Plan 06-04: Container Hardening

What was built (Tasks 1 & 2)

backend/Dockerfile — multi-stage with appuser

Replaced the single-stage root-running Dockerfile with a two-stage build:

Stage 1 AS builder:

  • FROM python:3.12-slim AS builder
  • Installs gcc (build-only dep, discarded after this stage) with --no-install-recommends
  • Runs pip install --no-cache-dir --prefix=/install -r requirements.txt

Stage 2 AS runtime:

  • FROM python:3.12-slim AS runtime
  • Installs runtime apt deps (tesseract-ocr, libgl1, libglib2.0-0) with --no-install-recommends
  • COPY --from=builder /install /usr/local (copies compiled packages, discards builder)
  • Creates appgroup gid=1000 and appuser uid=1000 (no login shell, no home dir)
  • COPY --chown=appuser:appgroup . .
  • USER appuser
  • CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] (no --reload)

docker-compose.yml — runtime hardening

Added to backend and celery-worker services (not celery-beat):

read_only: true
tmpfs:
  - "/tmp:mode=1777"
cap_drop:
  - ALL
security_opt:
  - "no-new-privileges:true"

Added comment before celery-beat:

# celery-beat: NOT hardened — writes celerybeat-schedule to working directory (Phase 6 Pitfall 7).

Existing bind mounts (./backend:/app), env vars, healthchecks, and Loki logging labels preserved.

Static verification (Tasks 1 & 2 acceptance criteria)

Check Result
FROM python:3.12-slim AS builder 1 ✓
FROM python:3.12-slim AS runtime 1 ✓
USER appuser 1 ✓
useradd --uid 1000 1 ✓
COPY --from=builder 1 ✓
--no-install-recommends occurrences 2 ✓
tesseract-ocr in runtime only 1 ✓
--reload in Dockerfile 0 ✓
docker compose config --quiet exit 0 ✓
read_only: true in backend block 1 ✓
/tmp:mode=1777 in backend block 1 ✓
no-new-privileges:true in backend block 1 ✓
read_only: true in celery-worker block 1 ✓
hardening keys in celery-beat block 0 ✓
./backend:/app bind mount preserved 1 ✓

Task 3: Human smoke test (PENDING)

Docker daemon was not running at execution time. Task 3 requires:

  1. docker compose up -d --build (rebuilds with new Dockerfile)
  2. docker compose exec -T backend id → must show uid=1000(appuser)
  3. docker compose exec -T backend sh -c 'touch /readonly_probe 2>&1 || echo "rootfs is read-only"'
  4. docker compose exec -T backend sh -c 'touch /tmp/ok && ls -la /tmp/ok'
  5. Same checks on celery-worker
  6. Confirm celery-beat is writable (intentionally)
  7. End-to-end upload test (proves tempfile.NamedTemporaryFile works under hardened runtime)

Human responds "approved" to advance to Wave 3 (06-06).

Satisfaction of requirements

  • D-07 (multi-stage non-root image): ✓ Tasks 1 & 2 complete
  • D-08 (read-only rootfs + tmpfs): ✓ Tasks 1 & 2 complete
  • D-09 (cap_drop ALL + no-new-privileges): ✓ Tasks 1 & 2 complete
  • Runtime verification (Task 3): PENDING human checkpoint