Repository navigation
Put the API in a container, with the models in it and the documents out - #34
Merged
Badri-Rajendran merged 3 commits intoSep 23, 2026
Merged
Conversation
Closes the first of ADR 0002's two deferred items. Multi-stage, non-root, and verified by running rather than by reading. Two properties of this app shaped it. It carries a model runtime — torch and two transformers are in the request path — and it must never carry the documents, which are 983 MB of issuers' PDFs kept strictly local (ADR 0016). So torch comes from PyTorch's CPU index on linux, which dropped every nvidia package and triton from the lock, and both models are baked in at the revisions config.py pins, with HF_HUB_OFFLINE set at runtime: a cold start loads them from the image, and a HuggingFace outage cannot take the API down. CI builds the image, runs it, and fails if /app/data is not empty. A .dockerignore regression is otherwise invisible until an image is gigabytes too large, and this is the rule that must not break quietly. Trivy fails the build on fixable HIGH or CRITICAL findings. One gunicorn worker, eight threads. Flask-Limiter has no shared storage, so a second worker would keep its own counters and double the 5/min on register and 10/min on login. A deliberate ceiling, recorded as one. Production also logs JSON to stdout, because a container's filesystem is ephemeral and nobody reads it; development is unchanged so an ingestion run's lines do not interleave with its progress output. Also here because the image made them matter: .env.example, which did not exist though three settings have no default; `make test`, `lint` and `check` so passing locally means what CI means; and tiktoken and anyio dropped, both imported nowhere, tiktoken in particular sitting next to core/text.py's deliberate approximation looking like an unfinished intention. Measured: 2.48 GB, torch 2.14.0+cpu, zero nvidia packages, no dev tools, and a container with no data/ directory answering "What is a deductible?" from the Postgres-stored index at 0.999.
The action's setup step runs an install script that exits 1 on the runner with nothing on stderr, so the scan never started and the job failed without saying why. Its published image is pinned instead, the same way gitleaks is installed by checksum rather than by an action. The image is scanned from a `docker save` tar with `--input`, so the scanner is not handed the docker socket.
Badri-Rajendran
force-pushed
the
feature_container_image
branch
from
September 22, 2026 20:43
1f6dcb9 to
923761c
Compare
Badri-Rajendran
force-pushed
the
feature_bm25_in_postgres
branch
from
September 22, 2026 20:43
cdd59d4 to
01ee2a3
Compare
A tag can be moved to different bytes, so pinning by tag is not pinning. The repository already SHA-pins its GitHub Actions and installs gitleaks by checksum; images were the gap. python:3.12-slim-bookworm, ghcr.io/astral-sh/uv and aquasec/trivy now carry name:tag@sha256, the tag kept beside the digest so the version is still readable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
CLAUDE.mdrequires a Docker build and image vulnerability scan in CI.docs/decisions/0002-ci-scope.md:31-36records it as deliberately deferred: "There is no Dockerfile for the app." This adds one, and closes the first of that ADR's two deferred items.Two properties of this app shape the image more than anything else:
torch(553 MB of the 1.1 GB venv),sentence-transformersand two models are in the request path.data/sbc/is 983 MB of issuers' PDFs, kept forever and strictly local (ADR 0016, ADR 0019).Implementation
/appholding onlysrc,migrationsandalembic.ini.[[tool.uv.index]]+ asys_platform == 'linux'marker, so macOS development is untouched. Re-locking removed everynvidia-*package andtriton.config.pypins, withHF_HUB_OFFLINE=1at runtime. No HuggingFace call on a cold start, and an HF outage cannot take the API down./app/datais not empty. A.dockerignoreregression is otherwise invisible until an image is gigabytes too large — this is the rule that must not break quietly. Trivy then fails on fixable HIGH/CRITICAL.Also here because the image made them matter:
.env.example, which did not exist although three settings have no default.make test/lint/check, so "it passed locally" means what CI means.tiktokenandanyiodropped — verified zero imports repo-wide.Validation — measured, not assumed
Two real defects were found by actually building it:
.dockerignoreexcluded*.md, butpyproject.tomldeclaresreadme = "README.md", so hatchling failed:OSError: Readme file does not exist: README.md.uv runre-syncs the default dependency groups, silently pullingruff,debugpy,jediandpipback in after--no-devhad removed them. Now the venv's python is invoked directly.The built image:
The decisive test — a container with no
data/directory, against the live database:Served over HTTP it returns the right counties for a ZIP (
{"counties":[{"county_fips":"12086","county_name":"Miami-Dade",...), 401 for an unauthenticated/api/auth/me, and access logs on stdout.uv run pytest -q— 415 passing.ruff checkclean.Security / DB / API impact
Note
Stacked on #33 → #32 → #31 → #30 → #29 → #28. CD to Azure is ADR 0002's second deferred item and the next PR.