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:
@@ -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.
|
||||
|
||||
|
||||
33
ROADMAP.md
33
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/<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
|
||||
|
||||
Reference in New Issue
Block a user