Beemer
Deploy: the 2026-09-21 code audit and its three verification rounds -- the quantity and relief allowlists, the five Canadian outcome readings, the IRPR s. 17 clock, the retrieval fixes and the performance work (approved_code 82525e1; curated baseline c32bd27 unchanged)
901bf8c Download Dockerfile from Beemer0/CanLex: direct link, hf CLI and curl.
- Browser
- Download file 4.19 kB
-
https://huggingface.co/spaces/Beemer0/CanLex/resolve/main/Dockerfile
- Command line
-
hf download hf://spaces/Beemer0/CanLex/Dockerfile
-
curl -L -o Dockerfile https://huggingface.co/spaces/Beemer0/CanLex/resolve/main/Dockerfile
4.19 kB
| # CanLex MCP server -- remote (streamable-HTTP) image. | |
| # | |
| # Builds anywhere: Hugging Face Spaces, Google Cloud Run, Fly.io, plain Docker. | |
| # Retrieval is fully local and key-free; the optional CanLII case citator reads | |
| # its key from the CANLII_API_KEY environment variable (supplied as a host | |
| # secret -- the key is never copied into the image). | |
| FROM python:3.12-slim | |
| # libgomp1 is the OpenMP runtime that onnxruntime (the reranker) links against. | |
| RUN apt-get update \ | |
| && apt-get install -y --no-install-recommends libgomp1 \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Run as a non-root user (UID 1000) -- required by Hugging Face Spaces. | |
| RUN useradd --create-home --home-dir /app --uid 1000 app | |
| WORKDIR /app | |
| # Python dependencies first, so this layer caches across code changes. The | |
| # image installs requirements.lock: the whole set, transitive dependencies | |
| # included, each pinned with its sha256 hashes -- `uv pip compile` of | |
| # requirements.txt for Linux / Python 3.12 (RUNBOOK.md, "Audit follow-up | |
| # implementation (2026-09-21): group C"). Hash-checking mode refuses any | |
| # distribution but the locked one, so every build installs the same set (until | |
| # the 2026-09-21 audit only the 8 direct pins were fixed and starlette, anyio | |
| # and the other transitives were re-resolved at each build). | |
| COPY requirements.lock . | |
| RUN pip install --no-cache-dir --require-hashes -r requirements.lock | |
| # Application code. | |
| COPY --chown=app:app canlex/ ./canlex/ | |
| # Pre-fetch the embedder and cross-encoder models so the cache is baked into the | |
| # image and the first request needs no network. The corpus is NOT re-embedded | |
| # here -- the committed embeddings.npz is used as-is; only the bge query model | |
| # (for embedding live queries) and the reranker are downloaded. | |
| # | |
| # Layer order (the code audit of 2026-09-21, optimization 9): this step reads | |
| # canlex/ and the network only -- Embedder() and Reranker() read nothing under | |
| # data/ at init -- so it runs BEFORE the corpus and curated COPYs below. Docker | |
| # rebuilds every layer after the first one whose input changed; after those | |
| # COPYs, every corpus refresh, curated change and composed release (which | |
| # rewrites embeddings.npz) re-ran this step and re-downloaded the 331 MB of model | |
| # files. Here only a change under canlex/ or to requirements.lock does. (Whether | |
| # the Hugging Face builder keeps layers between builds is outside our control.) | |
| # The user and environment it needs come first; the image's final user, | |
| # environment and files are what they were before the move. | |
| USER app | |
| # CANLEX_LOG_DIR turns on the local call log (canlex/calllog.py: one JSON line | |
| # per tool call, arguments included; loganalyze.py mines it). On the Space this | |
| # path is ephemeral -- lost on every restart or rebuild. The durable log is the | |
| # private Hugging Face dataset the server pushes to when the Space secrets | |
| # CANLEX_LOG_DATASET and CANLEX_LOG_TOKEN are set (RUNBOOK.md, "Call log"); the | |
| # HF_HUB_OFFLINE=1 below still refuses every other Hub request, model files | |
| # included -- calllog lets only that dataset's requests through. | |
| ENV HOME=/app \ | |
| HF_HOME=/app/.hf_cache \ | |
| CANLEX_HTTP=1 \ | |
| CANLEX_LOG_DIR=/tmp/canlex-logs \ | |
| PORT=7860 \ | |
| PYTHONUNBUFFERED=1 | |
| RUN python -c "from canlex.embed import Embedder; Embedder()" \ | |
| && python -c "from canlex.rerank import Reranker; Reranker()" | |
| # The processed corpus (section-chunk JSON) and the prebuilt semantic | |
| # embeddings (embeddings.npz, committed via git-LFS). Shipping the embeddings | |
| # as an artifact keeps the build fast: embedding ~20k chunks on the build host | |
| # took ~33 min and was tripping Hugging Face's job timeout as the corpus grew. | |
| # Regenerate with 'python -m canlex.embed' after any corpus change and commit | |
| # the result. | |
| COPY --chown=app:app data/processed/*.json ./data/processed/ | |
| COPY --chown=app:app data/processed/embeddings.npz ./data/processed/ | |
| # Curated commentary datasets (the canlex_us_disposition tool reads the | |
| # structured source file directly, not just its processed chunks). | |
| COPY --chown=app:app data/curated/ ./data/curated/ | |
| # From here on, model files are served from the baked cache, never fetched. | |
| ENV HF_HUB_OFFLINE=1 | |
| EXPOSE 7860 | |
| CMD ["python", "-m", "canlex.server"] | |