fix(backend): serve the production entrypoint, not the test factory
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 1m13s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 1m58s
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (pull_request) Successful in 1m13s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (pull_request) Successful in 1m59s

The image's CMD ran researchx.api.app:app - the module-global built by
create_app() with no arguments, which serves FakeModelAdapter and
cwd-based storage. Hosting it would have shipped fakes on the public
URL while /healthz answered 200 (P-3 closure; RESEARCH-v03 S8,
PLAN T-4.1). Now: researchx.api.main:app with --proxy-headers, so the
TLS front's forwarded headers are honoured.

Also pins uid/gid 10001 for the non-root user, so the host-side volume
chown in the deploy script is deterministic.
This commit is contained in:
2026-10-31 03:42:47 +00:00
parent 1eebe94beb
commit b964c60e72
+11 -3
View File
@@ -11,6 +11,14 @@
# so the volume mount point is the directory the app actually writes
# - /healthz is the public health surface (outside /api, never auth-wrapped,
# D-103) — the container healthcheck below uses it
# - the serving entrypoint is researchx.api.main (the PRODUCTION wiring:
# auth_enabled=True and cookie_secure=True hardwired, the live model
# factory reading per-user -> server -> keyless credentials from the
# runtime env file). researchx.api.app's module-global is the TEST/DEV
# factory — it serves FakeModelAdapter and cwd-based storage, so
# hosting it would silently ship fakes (P-3 closure, RESEARCH-v03 §8).
# - uid/gid 10001 is fixed so the host-side volume chown is deterministic
# (the deploy script reads the ownership from this image)
# ---- deps ----
FROM python:3.12-slim AS deps
@@ -36,8 +44,8 @@ ENV PATH="/app/.venv/bin:$PATH" \
RESEARCHX_DATA_DIR=/var/lib/researchx
COPY --from=deps /app/.venv /app/.venv
# Non-root app user (house recipe: frontend/Dockerfile runs as researchx).
RUN addgroup --system researchx \
&& adduser --system --ingroup researchx --no-create-home researchx \
RUN addgroup --gid 10001 researchx \
&& adduser --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin researchx \
&& mkdir -p /var/lib/researchx \
&& chown -R researchx:researchx /var/lib/researchx
USER researchx
@@ -47,4 +55,4 @@ EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=3)"]
# Single worker (REQ-83): the run registry lives in process memory.
CMD ["uvicorn", "researchx.api.app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
CMD ["uvicorn", "researchx.api.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--proxy-headers"]