◐ Off-By-One · answer catalog

docs-config-env-template-contradiction

1 answer(s)godocker

docs-config-env-template-contradiction

📦 Source in repository (JSON)

Answer

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).

Evidence & signatures

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}
Generated from the verified corpus · MIT licensedBack to the catalog