docs-config-env-template-contradiction
Root cause. GETTING-STARTED §3 described what the in-container environment looks like (postgres:5432) while .env.example ships host-facing values (localhost:5433). Users copied the template, saw the mismatch, and "fixed" it by adding prefixed variables like API_POSTGRES_HOST=postgres:5432 — which are dead keys the app never reads, so the container silently fell back to defaults.
The fix (GETTING-STARTED.md §3 note). Describe the template as-is, then explain where the switch to the service name happens:
> **Note:** The root `.env` is host-facing. The `POSTGRES_*` values shipped in
> `.env.example` use `POSTGRES_HOST=localhost` and `POSTGRES_PORT=5433`, which is
> the port the `postgres` service publishes on your machine (mapped `5433:5432`).
> No changes are needed when you run the API from your host.
>
> When the `api` container runs under Compose, it **overrides** `POSTGRES_HOST`
> and `POSTGRES_PORT` to `postgres:5432` in-container (see `docker-compose.yml`),
> so it talks to the `postgres` service over the internal network. `POSTGRES_USER`,
> `POSTGRES_PASSWORD`, and `POSTGRES_DB` are never overridden and always come from
> your root `.env`.
>
> Do **not** rename or prefix these variables (for example `API_POSTGRES_*`);
> Compose merges overrides by key, and a different prefix would create a second,
> unused variable while the container falls back to the wrong default.
The matching compose config (unchanged by the fix, but now documented truthfully):
services:
postgres:
image: postgres:16
ports:
- "5433:5432" # host-facing; docs describe THIS side
api:
environment:
POSTGRES_HOST: postgres # in-container override
POSTGRES_PORT: 5432 # in-container override
The rule, encoded as a test: when docs describe env behavior, diff against .env.example (the template) — never the live .env, which may carry developer-local overrides (e.g. a remote DB on 6543).
Reproduction built at `~/repro` (docs + `.env.example` + `docker-compose.yml` + 2 tests, stdlib + PyYAML only). 1. **Contradiction reproduced first** — with the broken note, `tests/test_docs_vs_template.py` failed: - `docs claim POSTGRES_HOST=postgres but .env.example (template) ships POSTGRES_HOST=localhost` - `docs claim POSTGRES_PORT=5432 but .env.example (template) ships POSTGRES_PORT=5433` 2. **After the fix, all tests pass:** - `test_docs_vs_template.py` — parses §3 claims and asserts they equal `.env.example` exactly → **PASS** - `test_compose_overrides.py` — asserts `api` overrides `POSTGRES_HOST=postgres`, `POSTGRES_PORT=5432`, and does **not** override `POSTGRES_USER/PASSWORD/DB` → **PASS** 3. **Live engine check** — `docker compose config` (Compose 2.40.3) shows the merged `api` environment as `POSTGRES_HOST: postgres`, `POSTGRES_PORT: "5432"`, confirming the in-container reality the docs now describe. 4. **Edge cases tested:** - *Live `.env` vs template*: wrote a dev `.env` with `db.internal.corp:6543`; the docs test still passes because it reads `.env.example`, while a naive docs-vs-live diff would fail — proving the rule. - *Port asymmetry*: host `5433` ≠ container `5432` is asserted on both sides (compose port mapping and docs claim). - *YAML type coercion*: `5432` parses as int; test compares `str()`-normalized values. - *Unnecessary prefixes*: the fixed note explicitly forbids `API_POSTGRES_*`; the template test fails if any docs claim introduces a key `.env.example` doesn't define.
{"model": "deepseek-v4-flash", "problem_class": "docs-config-env-template-contradiction", "result": "passed", "tests": 2}