diff --git a/PORTAINER.md b/PORTAINER.md index c044a0e..3cb7f79 100644 --- a/PORTAINER.md +++ b/PORTAINER.md @@ -4,7 +4,13 @@ DTF follows the same operating model as Graphs and ComporHUB: Gitea builds prebuilt images, pushes them to the Gitea registry, and calls one Portainer webhook. Portainer owns and redeploys one Docker Swarm stack named `dtf-cloud`. -The production stack is `deploy/stack.yaml`. It contains Site, Kanban, API, +The deployed stack is the repository's `docker-compose.yml`, which the +`dtf-cloud` Portainer stack points at. `deploy/stack.yaml` is a more hardened +definition that supplies every credential as a Docker secret rather than an +environment variable; it is not currently deployed. See `ROADMAP.md` 2.12 before +assuming either is authoritative. + +`deploy/stack.yaml` contains Site, Kanban, API, worker, PostgreSQL, ClamAV, and a one-time database initializer. Production uses Cloudflare R2, so MinIO is not part of this stack. diff --git a/ROADMAP.md b/ROADMAP.md index 3faa244..b37872e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -222,6 +222,39 @@ refactor: `'This runtime only supports APP_ENV=local'`, - Replace marker matching with behavioural assertions (import the module, assert the adapter classes in use). +### `[ ]` 2.12 — Production credentials are environment variables, and the docs name the wrong file + +Found 2026-09-21, by asking which file Portainer deploys. + +`PORTAINER.md` called `deploy/stack.yaml` "the production stack". The deployed +file is the repository's `docker-compose.yml`. They are not equivalent: + +| File | Credentials | Deployed | +|---|---|---| +| `deploy/stack.yaml` | 11 Docker secret files | no | +| `docker-compose.yml` | 8 plain environment variables | yes | + +So `DATABASE_PASSWORD`, `AWS_SECRET_ACCESS_KEY` and `OPERATOR_PASSWORD` sit in the +container environment, readable through `docker inspect`, `docker service inspect`, +the Portainer stack editor, and `/proc//environ` for anything in that +container. The R2 secret key is the worst of them: it grants read and write over +every customer's artwork. + +The `*_FILE` loading from 2.3 makes the hardened file bootable, so the work is +done — what remains is a decision, because changing how production receives its +credentials is not a change to make quietly: + +- **Migrate to `deploy/stack.yaml`:** create the 11 secrets in Portainer, point the + stack at that file. Best posture, most operational steps, and it also switches the + published ports (8080/8081 vs 18080/18081) so the reverse proxy needs updating. +- **Add Docker secrets to `docker-compose.yml`:** smaller change, keeps ports and + the current stack definition, still removes the values from the environment. +- **Accept it explicitly** and delete `deploy/stack.yaml` so two divergent + definitions stop drifting. + +Rotate the R2 key and operator password whichever is chosen, since the current +values have been readable from the stack environment. + ### `[x]` 2.6 — Base images are not pinned `(F10)` Dockerfiles default to mutable `python:3.12-slim` / `nginx:1.28-alpine`, the