docker-compose.yml silently drops 19 settings from .env, ten of which have no other way in #147

Closed
opened 2026-09-26 04:14:48 +00:00 by claude-bot · 2 comments
Collaborator

The bug

docker-compose.yml passes configuration to the container through an explicit environment: list. Inside the container, Settings reads the process environment, plus env_file=".env". That path resolves to /app/.env, which is never mounted. So a variable that is not on the compose list never reaches the app, however it is set in .env. Nothing warns about it: the value falls back to its default.

The list was written for v1.0.0 and did not grow with v1.1.0. These .env.example variables are not passed:

Env-only: currently impossible to set with the shipped compose file

variable feature effect today
METRICS_ENABLED #15 /metrics can never be enabled
BACKUP_REMOTE_PATH #18 off-site backup can never be enabled
BACKUP_S3_ENDPOINT, _BUCKET, _REGION, _ACCESS_KEY, _SECRET_KEY, _PREFIX #18 same
AURORA_ENABLED, AURORA_MIN_KP #20 aurora alerts can never be enabled

Runtime settings: the Settings page still works, only the .env seed is lost

RAMP_ON_MINUTES, RAMP_OFF_MINUTES, DAILY_BACKUP_RETAIN_COUNT, OVERNIGHT_OFF_ENABLED, OVERNIGHT_OFF_TIME, MORNING_ON_ENABLED, MORNING_ON_MODE, MORNING_ON_TIME, MORNING_ON_OFFSET_MIN

(HOST_PORT, IRIS_UID and IRIS_GID are compose-interpolation-only by design and are fine.)

The v1.1.0 CHANGELOG and docs/setup.md document the first group as working by setting them in .env. With the shipped compose file, they don't.

Why nothing caught it

Unit tests construct Settings directly, and the E2E container in CI is started without these variables, so no test crosses the compose → container boundary.

Fix

  1. Replace the hand-maintained list with env_file: .env. That passes every variable, and there is no list left to drift.
  2. Keep the SESSION_SECRET guard, or move it into the app first. The compose ${SESSION_SECRET:?…} is currently the only thing that stops a start without a secret. The app itself (app/main.py) only logs a CRITICAL when the secret is the built-in default change-me-in-production, then starts anyway and signs cookies with a publicly known key. Make the app refuse to start in that case unless DISABLE_AUTH=true, then drop the compose-side guard.
  3. Add a test that fails when .env.example names a variable that app/config.py does not define, and one that fails when the container cannot see a variable set in .env. For the second, start the compose stack in CI with a sentinel value (for example METRICS_ENABLED=true) and assert that /metrics answers.
  4. CHANGELOG entry under Fixed, and a v1.1.1 patch release: it is a documented-feature regression.

Impact on the production deploy

The production role in Rhoving/iac-repo should not copy the repo's compose file until this is fixed. It should render its own with env_file:, and must itself guarantee that SESSION_SECRET is set. That is noted in the deployment spec.

## The bug `docker-compose.yml` passes configuration to the container through an explicit `environment:` list. Inside the container, `Settings` reads the process environment, plus `env_file=".env"`. That path resolves to `/app/.env`, which is never mounted. So **a variable that is not on the compose list never reaches the app**, however it is set in `.env`. Nothing warns about it: the value falls back to its default. The list was written for v1.0.0 and did not grow with v1.1.0. These `.env.example` variables are not passed: **Env-only: currently impossible to set with the shipped compose file** | variable | feature | effect today | |---|---|---| | `METRICS_ENABLED` | #15 | `/metrics` can never be enabled | | `BACKUP_REMOTE_PATH` | #18 | off-site backup can never be enabled | | `BACKUP_S3_ENDPOINT`, `_BUCKET`, `_REGION`, `_ACCESS_KEY`, `_SECRET_KEY`, `_PREFIX` | #18 | same | | `AURORA_ENABLED`, `AURORA_MIN_KP` | #20 | aurora alerts can never be enabled | **Runtime settings: the Settings page still works, only the `.env` seed is lost** `RAMP_ON_MINUTES`, `RAMP_OFF_MINUTES`, `DAILY_BACKUP_RETAIN_COUNT`, `OVERNIGHT_OFF_ENABLED`, `OVERNIGHT_OFF_TIME`, `MORNING_ON_ENABLED`, `MORNING_ON_MODE`, `MORNING_ON_TIME`, `MORNING_ON_OFFSET_MIN` (`HOST_PORT`, `IRIS_UID` and `IRIS_GID` are compose-interpolation-only by design and are fine.) The v1.1.0 CHANGELOG and `docs/setup.md` document the first group as working by setting them in `.env`. With the shipped compose file, they don't. ## Why nothing caught it Unit tests construct `Settings` directly, and the E2E container in CI is started without these variables, so no test crosses the compose → container boundary. ## Fix 1. Replace the hand-maintained list with `env_file: .env`. That passes every variable, and there is no list left to drift. 2. **Keep the `SESSION_SECRET` guard, or move it into the app first.** The compose `${SESSION_SECRET:?…}` is currently the *only* thing that stops a start without a secret. The app itself (`app/main.py`) only logs a CRITICAL when the secret is the built-in default `change-me-in-production`, then starts anyway and signs cookies with a publicly known key. Make the app refuse to start in that case unless `DISABLE_AUTH=true`, then drop the compose-side guard. 3. Add a test that fails when `.env.example` names a variable that `app/config.py` does not define, **and** one that fails when the container cannot see a variable set in `.env`. For the second, start the compose stack in CI with a sentinel value (for example `METRICS_ENABLED=true`) and assert that `/metrics` answers. 4. CHANGELOG entry under Fixed, and a v1.1.1 patch release: it is a documented-feature regression. ## Impact on the production deploy The production role in `Rhoving/iac-repo` should not copy the repo's compose file until this is fixed. It should render its own with `env_file:`, and must itself guarantee that `SESSION_SECRET` is set. That is noted in the deployment spec.
Author
Collaborator

Picking this up. Plan, as in the body:

  • env_file: .env replaces the list.
  • The SESSION_SECRET guard moves into the app, which will refuse to start with the default secret while auth is on.
  • A pytest that .env.example agrees with Settings.
  • A CI step that renders the compose config with sentinel values and asserts they reach the container environment. The E2E container is started with docker run, so today nothing crosses that boundary.
  • Released as v1.1.1, so the production deploy (Rhoving/iac-repo#527) can pin a fixed tag.
Picking this up. Plan, as in the body: - `env_file: .env` replaces the list. - The `SESSION_SECRET` guard moves into the app, which will refuse to start with the default secret while auth is on. - A pytest that `.env.example` agrees with `Settings`. - A CI step that renders the compose config with sentinel values and asserts they reach the container environment. The E2E container is started with `docker run`, so today nothing crosses that boundary. - Released as v1.1.1, so the production deploy (Rhoving/iac-repo#527) can pin a fixed tag.
Author
Collaborator

Fixed in #160 and verified in CI. It ships as v1.1.1, whose CHANGELOG is finalised in #161.

What changed:

  • env_file: .env replaces the environment: list, so every .env variable now reaches the container.
  • SESSION_SECRET: the app now refuses to start on the default secret while auth is enabled. The check is assert_session_secret_is_safe in app/config.py, called at import in app/main.py. An empty env value is caught too, because env_ignore_empty falls back to the default.
  • Guards against a repeat:
    • test_env_example.py ties .env.example to Settings. The allowlist is HOST_PORT, IRIS_UID and IRIS_GID.
    • test_config_guard.py covers four guard cases.
    • A CI step in docker-e2e renders docker compose config against a sentinel .env and asserts five values arrive. On #160's run it logged compose env_file passthrough OK. Locally it was also shown to fail against a reverted list.
  • Docs: the spec §13.1 compose example is corrected (it was also pointing at the wrong custom_events.yaml path), and the ASVS note and CHANGELOG are updated, including an upgrade note that forgotten .env values now take effect.

For Rhoving/iac-repo#527: rendering its own compose with env_file: is still right. The role should still assert SESSION_SECRET, but the app is now a second line of defence.

Fixed in #160 and verified in CI. It ships as v1.1.1, whose CHANGELOG is finalised in #161. What changed: - **`env_file: .env`** replaces the `environment:` list, so every `.env` variable now reaches the container. - **`SESSION_SECRET`:** the app now refuses to start on the default secret while auth is enabled. The check is `assert_session_secret_is_safe` in `app/config.py`, called at import in `app/main.py`. An empty env value is caught too, because `env_ignore_empty` falls back to the default. - **Guards against a repeat:** - `test_env_example.py` ties `.env.example` to `Settings`. The allowlist is `HOST_PORT`, `IRIS_UID` and `IRIS_GID`. - `test_config_guard.py` covers four guard cases. - A CI step in `docker-e2e` renders `docker compose config` against a sentinel `.env` and asserts five values arrive. On #160's run it logged `compose env_file passthrough OK`. Locally it was also shown to fail against a reverted list. - **Docs:** the spec §13.1 compose example is corrected (it was also pointing at the wrong `custom_events.yaml` path), and the ASVS note and CHANGELOG are updated, including an upgrade note that forgotten `.env` values now take effect. For Rhoving/iac-repo#527: rendering its own compose with `env_file:` is still right. The role should still assert `SESSION_SECRET`, but the app is now a second line of defence.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Iris-WLED#147
No description provided.