3.2 KiB
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 |
|
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
appgroupgid=1000 andappuseruid=1000 (no login shell, no home dir) COPY --chown=appuser:appgroup . .USER appuserCMD ["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:
docker compose up -d --build(rebuilds with new Dockerfile)docker compose exec -T backend id→ must show uid=1000(appuser)docker compose exec -T backend sh -c 'touch /readonly_probe 2>&1 || echo "rootfs is read-only"'docker compose exec -T backend sh -c 'touch /tmp/ok && ls -la /tmp/ok'- Same checks on celery-worker
- Confirm celery-beat is writable (intentionally)
- 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