Merge branch 'milestone/v0.3-hosted' into po/phase4-deploy-two-service
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 1m12s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 2m15s
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 2m8s
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 1m12s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 2m15s
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 2m8s
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# Build-context hygiene for the backend image (mirrors frontend/.dockerignore).
|
||||
.venv
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
.pytest_cache
|
||||
.mypy_cache
|
||||
.ruff_cache
|
||||
.git
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
var
|
||||
tests
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
README.md
|
||||
@@ -0,0 +1,58 @@
|
||||
# ResearchX backend — production release image (ciagent release system)
|
||||
# Multi-stage: deps -> runtime. FastAPI + uvicorn, single worker, non-root.
|
||||
# Built ON the Docker host from the synced source (no local docker required),
|
||||
# mirroring frontend/Dockerfile: the build context is backend/.
|
||||
#
|
||||
# Contracts this image holds (REQ-83, v0.3 line):
|
||||
# - one uvicorn worker — the run registry lives in process memory
|
||||
# - non-root user "researchx" — house recipe, same as frontend/Dockerfile
|
||||
# - the mutable data root is RESEARCHX_DATA_DIR (default /var/lib/researchx);
|
||||
# that is the variable researchx.config.paths.data_root() reads (D-107),
|
||||
# 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
|
||||
# uv is the repo's package manager (backend/uv.lock). The repo pins no uv
|
||||
# version, so this install is bounded to a major range; pin it exactly once
|
||||
# the deploy host's uv version is known.
|
||||
RUN pip install --no-cache-dir "uv>=0.8,<2"
|
||||
WORKDIR /app
|
||||
ENV UV_PYTHON_DOWNLOADS=never
|
||||
COPY pyproject.toml uv.lock .python-version ./
|
||||
COPY src ./src
|
||||
# --frozen: use uv.lock as written (no lock rewrite). --no-dev: no test/lint
|
||||
# dependencies in the runtime image. --no-editable: copy the package into
|
||||
# site-packages instead of linking the package source tree.
|
||||
RUN uv sync --frozen --no-dev --no-editable --no-cache --python 3.12
|
||||
|
||||
# ---- runtime ----
|
||||
FROM python:3.12-slim AS runtime
|
||||
WORKDIR /app
|
||||
ENV PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
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 --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
|
||||
EXPOSE 8000
|
||||
# Public health surface (outside /api, never auth-wrapped). python is already
|
||||
# in the image, so the check needs no extra package.
|
||||
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.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--proxy-headers"]
|
||||
Reference in New Issue
Block a user