# atrium-page-classification — the published environment contract (atrium-project#60). # # Copy to `.env` and edit. Do not commit `.env` (gitignored). # # This file is the COMPLETE ledger: every variable the api image reads, at the # default that is in the CODE. The operator-facing subset across all five services is # the table in the hub's docs/k8s_deployment.md; this file's layout is fixed by # docs/templates/env.example.template in ufal/atrium-project. # # This is the SHORTEST of the five, and that is the correct output rather than a # gap. page-classification's service layer reads nothing beyond the shared set: # service/inference.py, service/document_json.py and service/api_client.py contain # no environment reads at all, and the batch CLI (run.py, classifier.py, # ensemble.py, model_registry.py …) is configured entirely through argparse and # config.txt. A short contract here means the service has few deployment knobs, # not that some were missed. # # TAG LEGEND: [required] raises at startup without it · [secret] never a literal # anywhere committed, use a k8s Secret · [operator] an ordinary deployment knob · # [algorithmic] changes what the service COMPUTES, not how it is deployed · # [build] read by compose/Dockerfile or baked at build time # # HOW THIS FILE REACHES THE PROCESS # docker compose → delivered. docker-compose.yml's `api` service declares # `env_file: .env`, so everything below is injected into the # container. Two names (ALLOWED_ORIGINS, PORT) are ALSO named # explicitly in the `environment:` block, because they need a # value different from a bare pass-through (compose's own # narrower ALLOWED_ORIGINS default — see below) or because # compose itself needs the value (PORT, for the `ports:` # mapping). # Kubernetes → NOT delivered. Use the manifest's own env: block. See # ufal/atrium-project docs/k8s_deployment.md. # python -m service.api # → NOT delivered. Nothing here calls load_dotenv(); export the # values yourself: set -a; . ./.env; set +a # # BLANK IS NOT UNIVERSALLY "UNSET". ALLOWED_ORIGINS is the trap: a blank value parses # to an EMPTY origin list and blocks every browser request, where omitting the line # gives you `*` — so it ships commented below, not blank. # ── §0 Required before first start ───────────────────────────────────────────── # None — this service starts with no variables set. # ── §1 HTTP surface ───────────────────────────────────────────────────────────── # [operator] Port the api service binds, and the port service/healthcheck.py probes # inside the container. docker-compose.yml maps "${PORT:-8000}:${PORT:-8000}", so # changing it here moves listener, published port and HEALTHCHECK together. In # Kubernetes, change `containerPort` in the same edit. (atrium-project#58) PORT=8000 # [operator] Bind address. Leave at 0.0.0.0. service/healthcheck.py always probes # 127.0.0.1 by design and never reads HOST, so 127.0.0.1 here gives a container that # reports HEALTHY to every probe while being unreachable from outside it. This is the # one value whose failure mode is silent. # HOST=0.0.0.0 # [operator] CSV of CORS origins. Code default `*` (every origin) — # service/atrium_service.py:370 falls back to it when unset. docker-compose.yml # supplies its own narrower default (http://localhost:8080,http://127.0.0.1:8080), # which applies ONLY under compose; note it spells loopback differently from # alto-postprocess's compose default, and `localhost` and `127.0.0.1` are DIFFERENT # origins to a browser. Kubernetes never reads docker-compose.yml, so a k8s # deployment that omits this runs `*`. Ships commented so an operator who leaves it # alone gets the real code default under compose; uncomment to change it. # ALLOWED_ORIGINS=* # [limit] Canonical upload limit in megabytes, per uploaded part. 10 is THIS repo's # built-in (tool_limits.py, `upload_limit(10)`); the five services do not share one # value — alto 25, llm-enrich 10, nlp-enrich 5, page-classification 10, translator 50. # Applies to the file of both /predict_image and /predict_document, and to their # `document_json` part. Over it → 413 limit_exceeded. Default: 10. MAX_UPLOAD_MB=10 # [deprecated] Do not set. A byte-valued fallback kept working for one release for # callers that already exported it (strategy §4.5); MAX_UPLOAD_MB wins whenever both # are present (service/atrium_service.py:353-364). Listed commented-out so the # contract is complete and the deprecation is discoverable. # MAX_UPLOAD_BYTES= # [operator] Path the container's own HEALTHCHECK probes. Change it only alongside # the route itself. # HEALTHCHECK_PATH=/health # ── §2 Lifecycle ──────────────────────────────────────────────────────────────── # [operator] Seconds uvicorn waits for in-flight requests after SIGTERM. A # multi-page PDF is classified synchronously inside the request, so a large # document can legitimately outlast the default — raise this together with the # deployment's grace period (terminationGracePeriodSeconds in k8s, see # docs/k8s_deployment.md in the hub; stop_grace_period under compose). GRACEFUL_SHUTDOWN_S=20 # [operator] Root logger level for the `python -m service.api` start path. All five # services honour it identically (atrium-project#61). LOG_LEVEL=INFO # [operator] Filesystem auto-reload. Development only — never in a deployment. # Deliberately NOT wired into any compose or k8s template. # RELOAD=false # ── §3 Backing services (atrium-project#63) ───────────────────────────────────── # None — this service calls no attachable backing service. # ── §4 Image / provenance ──────────────────────────────────────────────────────── # [build] Image tag used by docker-compose: on release, the version WITHOUT the leading # `v` (the git tag says `v1.8.0-beta`, GHCR carries `1.8.0-beta` -- atrium-project#69, # roadmap B3). Read only by docker-compose.yml; no Python here reads it. ATRIUM_VERSION=dev # [build] Baked at build time; stamped into paradata as runner_ref. ATRIUM_RUNNER_REF=dev # [build] Override either only if you publish under another name. # ATRIUM_RUNNER_IMAGE=ghcr.io/ufal/atrium-page-classification-api:dev # ATRIUM_RUNNER_REPO=https://github.com/ufal/atrium-page-classification # [build] The uid docker-compose.yml runs the containers as (`user: ${ATRIUM_UID:-10001}:0`); # no Python here reads it. On Linux set it to your own uid -- `echo "ATRIUM_UID=$(id -u)" >> .env` # (compose does not run `$(...)` itself) -- so the container can write ./data, which a clone # owns as you. Docker Desktop needs nothing. A named volume created by an older image is # writable by uid 10001 only: recreate it once (`docker compose down -v`) after setting # this. (atrium-project#69, roadmap B6) # ATRIUM_UID=10001 # [build] Correlation id copied verbatim into paradata `request_id`. Read ONCE from # the process environment at import (atrium_paradata.py's module-level constants), # so it identifies a batch run, not an individual HTTP request. # ATRIUM_REQUEST_ID= # [operator] The IRI (e.g. a ROR id) of the organisation that operates this deployment, # recorded as the `agent` of every run's CreateAction (atrium-project#71). Unset, no agent # is recorded; never set it to a processing account, which only reads and calls back. # ATRIUM_RUN_AGENT= # [build] Where the HuggingFace model cache lives. The Dockerfile sets this and # docker-compose.yml mounts a named volume at it; without the volume the cache is # re-downloaded on every start. # HF_HOME=/cache/huggingface # [build] Batch CLI only. `run.py --models-dir DIR` sets HF_HOME to DIR/hf, and, without # --hf, switches the Hugging Face libraries offline, so a run reads the staged models and # never downloads (Galaxy, atrium-project#66). Set them yourself to forbid downloads in # any batch run; the api service does not set them. # HF_HUB_OFFLINE=1 # TRANSFORMERS_OFFLINE=1 # [build] Optional — only for gated HuggingFace models or `push_to_hub`. Not read # by the api service itself. # HF_TOKEN= # ── §5 Service knobs — operator ────────────────────────────────────────────────── # No knob other than the limits below. # # Limits (atrium-project#53) — declared in tool_limits.py, reported with their current # value in GET /info (`limits`; `limits_meta` names the variable), and listed in # service/README.md's `## Limits` table. A malformed value stops the service at startup, # naming the variable; blank means "use the default". # # [limit] Most pages one /predict_document call classifies (the whole PDF, or its `pages` # selection), checked before any page is rendered. Over it → 413 limit_exceeded. Default: 50. MAX_PDF_PAGES=50 # [limit] Resolution PDF pages are rendered at before classification. 300 is how the # training pages were made (data_scripts/unix/pdf2png.sh); changing it changes what the # models see. Default: 300. PDF_RENDER_DPI=300 # [limit] Largest image the service decodes, in pixels (width × height): an uploaded # image, or a PDF page at PDF_RENDER_DPI, sized before it is rendered. Over it → 413 # limit_exceeded. Default: 178956970 (Pillow's own decompression-bomb threshold). MAX_IMAGE_PIXELS=178956970 # ── §6 Completeness ledger — algorithmic, deprecated, unreachable ──────────────── # None — this repo has no algorithmic or unreachable environment reads; the batch # CLI is configured entirely through argparse and config.txt.