docs: name the stack Portainer actually deploys, and its credential exposure

PORTAINER.md called deploy/stack.yaml the production stack. The deployed file is
docker-compose.yml, which supplies eight credentials as plain environment
variables where stack.yaml uses Docker secrets. That puts the database password,
operator password and the R2 secret key in the container environment, readable
through docker inspect and the Portainer stack editor.

Recorded as ROADMAP 2.12 with the three options rather than changed here:
altering how production receives credentials is not a quiet change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-21 13:42:12 -03:00
parent c1a07a75fa
commit e95a42dbed
2 changed files with 40 additions and 1 deletions

View File

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

View File

@@ -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/<pid>/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