Phase 4 slice 1: backend release image (single worker, non-root) #31

Closed
coreci-researchx-po wants to merge 2 commits from po/phase4-backend-dockerfile into main
Collaborator

What — adds the backend release image. Two new files, no application code touched:

  • backend/Dockerfile — multi-stage like frontend/Dockerfile: deps stage installs the dependencies from the repo lock (backend/uv.lock, uv sync --frozen --no-dev --no-editable), runtime stage runs uvicorn researchx.api.app:app --host 0.0.0.0 --port 8000 --workers 1 as the non-root user researchx, with the data dir at /var/lib/researchx.
  • backend/.dockerignore — keeps .env*, caches and the local data dir out of the build context.

Why — REQ-83 of the owner-directed roadmap: "Dockerfile for the FastAPI backend (single uvicorn worker, non-root)". Tracked on coreci/researchx#30. The frontend has had a release image since v0.1; the backend has none, so there is nothing deployable for the API today.

How to review — two files, in this order:

  1. backend/Dockerfile — the deps stage reads the committed lock, not the index; the runtime stage drops the build toolchain and the dev dependencies, and runs as a non-root user.
  2. backend/.dockerignore — secrets and caches stay out of the image.

Done looks like: the deploy host builds this image from the backend/ context the way deploy.sh builds the frontend, and the container answers on port 8000 as user researchx.

Risk — low: new files only, nothing runs them yet. Two legs this slice deliberately does not close, both named in REQ-83 and both needing application code, not Docker:

  • /healthz does not exist. Every route in the app sits under /api; the deploy health check for the backend has no endpoint until that unauthenticated route is added.
  • RESEARCHX_DATA_DIR is not read by the app. Storage is Path.cwd() / ".researchx" (backend/src/researchx/api/app.py, create_app). The image links that path at /var/lib/researchx, so a volume mounted there works today; the link becomes a no-op once the app reads the variable.

Two smaller notes: no uv version is pinned anywhere in the repo, so the image installs a bounded range (uv>=0.8,<2) — pin it exactly once the deploy host's uv version is known; and no secret is baked into the image (key injection rides REQ-85's hosted live mode).

Evidence — repo gate green on main at merge commit 57528d7473ca (run 58204, backend job 60493 and frontend job 60494 both success); branch po/phase4-backend-dockerfile off main @ 57528d7473ca. No image build was run: the repo gate does not build images and this seat has no Docker, so the first build is on the deploy host under the deploy.sh extension (REQ-83).

**What** — adds the backend release image. Two new files, no application code touched: - `backend/Dockerfile` — multi-stage like `frontend/Dockerfile`: deps stage installs the dependencies from the repo lock (`backend/uv.lock`, `uv sync --frozen --no-dev --no-editable`), runtime stage runs `uvicorn researchx.api.app:app --host 0.0.0.0 --port 8000 --workers 1` as the non-root user `researchx`, with the data dir at `/var/lib/researchx`. - `backend/.dockerignore` — keeps `.env*`, caches and the local data dir out of the build context. **Why** — REQ-83 of the owner-directed roadmap: "Dockerfile for the FastAPI backend (single uvicorn worker, non-root)". Tracked on coreci/researchx#30. The frontend has had a release image since v0.1; the backend has none, so there is nothing deployable for the API today. **How to review** — two files, in this order: 1. `backend/Dockerfile` — the deps stage reads the committed lock, not the index; the runtime stage drops the build toolchain and the dev dependencies, and runs as a non-root user. 2. `backend/.dockerignore` — secrets and caches stay out of the image. Done looks like: the deploy host builds this image from the `backend/` context the way `deploy.sh` builds the frontend, and the container answers on port 8000 as user `researchx`. **Risk** — low: new files only, nothing runs them yet. Two legs this slice deliberately does not close, both named in REQ-83 and both needing application code, not Docker: - **`/healthz` does not exist.** Every route in the app sits under `/api`; the deploy health check for the backend has no endpoint until that unauthenticated route is added. - **`RESEARCHX_DATA_DIR` is not read by the app.** Storage is `Path.cwd() / ".researchx"` (`backend/src/researchx/api/app.py`, `create_app`). The image links that path at `/var/lib/researchx`, so a volume mounted there works today; the link becomes a no-op once the app reads the variable. Two smaller notes: no uv version is pinned anywhere in the repo, so the image installs a bounded range (`uv>=0.8,<2`) — pin it exactly once the deploy host's uv version is known; and no secret is baked into the image (key injection rides REQ-85's hosted live mode). **Evidence** — repo gate green on `main` at merge commit `57528d7473ca` (run 58204, backend job 60493 and frontend job 60494 both success); branch `po/phase4-backend-dockerfile` off `main` @ `57528d7473ca`. No image build was run: the repo gate does not build images and this seat has no Docker, so the first build is on the deploy host under the deploy.sh extension (REQ-83).
coreci-researchx-po added 2 commits 2026-10-30 23:02:11 +00:00
Phase 4 slice 1: backend/Dockerfile (single worker, non-root)
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 33s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 1m36s
9bdbfaa267
Phase 4 slice 1: backend/.dockerignore (build-context hygiene)
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 32s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 1m36s
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (pull_request) Successful in 32s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (pull_request) Successful in 1m36s
d51c66d45b
coreci-hap was assigned by coreci-researchx-po 2026-10-30 23:02:15 +00:00
coreci-cpo was assigned by coreci-researchx-po 2026-10-30 23:02:15 +00:00
Author
Collaborator

TL;DR — closing this one in favour of coreci/researchx#32: the base branch is wrong for phase 4, and two of its notes are wrong for the v0.3 code.

What —

  • Base: phase 4 ships on milestone/v0.3-hosted — every v0.3 phase so far landed there (PRs #16..#19) and tags v0.3.0..v0.3.3 are cut from it. This pull request is based on main (57528d7473), which has diverged from the milestone branch (14 commits only on main, 8 only on the branch) and still carries the v0.2-era app, so merging here would land the backend image on a branch the phase does not ship from.
  • Notes corrected by reading the v0.3 code: /healthz does exist (a plain GET outside /api, never auth-wrapped — D-103), and the mutable data root does come from RESEARCHX_DATA_DIR (researchx.config.paths.data_root() — D-107). The image in #32 is written against those contracts, with a real healthcheck and a real volume mount.

Action — superseded by coreci/researchx#32 (same file set, corrected, based on milestone/v0.3-hosted @ fd3d6c2e03 = tag v0.3.3, with the phase-4 gate ported onto that branch). Nothing to merge here.

Details — coreci/researchx#32.

**TL;DR** — closing this one in favour of coreci/researchx#32: the base branch is wrong for phase 4, and two of its notes are wrong for the v0.3 code. **What** — - Base: phase 4 ships on `milestone/v0.3-hosted` — every v0.3 phase so far landed there (PRs #16..#19) and tags v0.3.0..v0.3.3 are cut from it. This pull request is based on `main` (57528d7473), which has diverged from the milestone branch (14 commits only on `main`, 8 only on the branch) and still carries the v0.2-era app, so merging here would land the backend image on a branch the phase does not ship from. - Notes corrected by reading the v0.3 code: `/healthz` does exist (a plain GET outside `/api`, never auth-wrapped — D-103), and the mutable data root does come from `RESEARCHX_DATA_DIR` (`researchx.config.paths.data_root()` — D-107). The image in #32 is written against those contracts, with a real healthcheck and a real volume mount. **Action** — superseded by coreci/researchx#32 (same file set, corrected, based on `milestone/v0.3-hosted` @ fd3d6c2e03 = tag v0.3.3, with the phase-4 gate ported onto that branch). Nothing to merge here. **Details** — coreci/researchx#32.
Some checks are pending
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 32s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 1m36s
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (pull_request) Successful in 32s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (pull_request) Successful in 1m36s

Pull request closed

Sign in to join this conversation.