Repository navigation
Production image installs from pyproject.toml, not uv.lock #57
Description
Context: surfaced by Copilot review on #56 (IDC v25), where it caused a concrete, user-visible drift risk. That PR fixed the symptom for one dependency; this issue is the root cause.
Summary
uv.lock constrains CI but not the shipped image. The Docker build resolves dependencies fresh from the lower bounds in pyproject.toml, so the same git commit can produce different images at different times, and the versions CI tested and audited are not necessarily the versions that ship.
Mechanism
The two install paths diverge:
| Command | Resolves from | |
|---|---|---|
| CI | uv sync --extra dev (.github/workflows/ci.yml:49) |
uv.lock |
| Image | uv pip install --system . (Dockerfile:14-16) |
pyproject.toml bounds |
The Dockerfile copies pyproject.toml, README.md and src — never uv.lock. Every runtime dependency except idc-index is a lower bound (duckdb>=1.5.5, pyarrow>=25.0.0, pydantic>=2.7, fastapi>=0.141.1, uvicorn[standard]>=0.52.1, mcp>=1.29.0,<2), so each rebuild re-resolves to whatever is newest at that moment.
Why it matters
- CI's guarantees do not transfer to the artifact.
pytest,banditand — most importantly —pip-auditall run against the locked set. A clean vulnerability scan says nothing about the image, which may contain a different, never-audited version of any dependency. - Rebuilds are not reproducible. Per
dev/deployment.md, prod runs test's exact bytes and never rebuilds, so the exposure window is the image build in test. But "build the canonical image from this commit" is not currently a deterministic operation, which is the property the promotion model assumes. - Data-version drift was the visible case.
idc-indextransitively pinsidc-index-data, which is the IDC release advertised at/v3/version. Underidc-index>=0.13.0, rebuilding the v25 commit after a later release shipped would have served a different dataset than the changelog claimed. feat: serve IDC v25 (idc-index 0.13.0) #56 pinsidc-index==0.13.0to stop that — a point fix for the one dependency where drift is user-visible, not a general solution.
Options
A. uv sync --frozen in the image. Most direct, but uv sync builds a project .venv rather than installing system-wide, so the DuckDB bake step (RUN python -c ...) and the CMD both need repointing at /app/.venv/bin/. Larger diff.
B. Export the lock to a requirements file, then install as today.
COPY pyproject.toml uv.lock README.md ./
COPY src ./src
RUN uv export --frozen --no-dev --format requirements-txt > /tmp/requirements.txt \
&& uv pip install --system -r /tmp/requirements.txt \
&& uv pip install --system --no-deps .Keeps --system and leaves the rest of the Dockerfile untouched. Probably the smaller change.
C. Status quo — pin each dependency exactly in pyproject.toml. Works, but duplicates the lockfile by hand and drifts from it silently.
Preference is B unless the .venv layout is wanted for other reasons.
Notes
- Whichever option lands,
Dockerfileneedsuv.lockin its build context, and theuv.lockpath filters already present inci.ymlandbuild-and-deploy-dev.ymlmean lock changes will correctly trigger a rebuild. - Worth deciding at the same time whether the
idc-index==0.13.0pin from feat: serve IDC v25 (idc-index 0.13.0) #56 stays. It remains useful as an explicit statement of the advertised data release even once the lock is authoritative, anddev/deployment.md § Updating for a new IDC releasealready describes bumping it as the way to move IDC versions. - Verify after the change that the image still reports the expected
/v3/versionand that the baked DuckDB build step runs in the same environment the deps landed in.
Drafted with Claude Code.
Activity