[config] refusing to start — 1 problem(s) with the environment:
[config] - UPLOADS_DIR is required and is not set.
What happened
#64 added UPLOADS_DIR to the always-required list, on the reasoning that its fallback of /app/uploads is correct inside the container and wrong everywhere else, so nothing should inherit it silently. That reasoning stands.
What was missed is that docker-compose.qa.yml never set it either. QA had always relied on exactly the fallback #64 set out to stop people relying on. So the first deploy after #64 merged is the first one that fails.
This was an incomplete check on my part, and specifically a check that looked confident. #64's verification confirmed both CI workflows set every always-required variable, and reported that — but CI is not what deploys. The compose file, which is, was never looked at.
The second half: setting it in the Portainer stack does not help
ADMIN_GATE_SECRET was added to the stack's environment variables and the container still reports it unset. That is not a mistake — it is how compose works, and it is the same mechanism #87 already documented:
Compose only forwards variables explicitly declared in environment:.
Portainer's stack variables are available for interpolation into the compose file as ${VAR}. They are not handed to the container. A service receives exactly what its environment: block lists, and nothing else.
So both variables need a line in the compose file, and they need different treatment:
Variable
Line
Why
UPLOADS_DIR
UPLOADS_DIR=/app/uploads
Not a secret, and it must match the right-hand side of the volume mapping. Hardcoded so it cannot drift from the mount.
ADMIN_GATE_SECRET
ADMIN_GATE_SECRET=${ADMIN_GATE_SECRET}
A secret, so the value stays in the stack environment and only the reference lives in the repo.
The header comment listing required stack variables needs ADMIN_GATE_SECRET added too, since it is now one of them.
Production is exposed to the same failure
Production runs from a Portainer stack outside this repository and has the same history — it will have been relying on the /app/uploads fallback as well. It will refuse to boot on its next rebuild unless UPLOADS_DIR is set there first. That is not fixed by this change, and it should be done before the next production deploy rather than discovered during one.
The wider lesson worth recording
#64's checks answered "does CI still pass" when the question that mattered was "will this still deploy". Those are different questions, and a required-configuration change is precisely the case where the second one is the one to ask. Any future change to the always-required list should be checked against docker-compose.qa.yml and the production stack, not just the workflows.
Verification
docker compose -f docker-compose.qa.yml config renders both variables with the expected values, and the QA container boots with no [config] refusing to start line — and, since ADMIN_GATE_SECRET will now be present, without the admin-gate warning either.
Severity
High while it stands. QA is down and production is one rebuild away from the same.
The QA container refuses to boot:
```
[config] refusing to start — 1 problem(s) with the environment:
[config] - UPLOADS_DIR is required and is not set.
```
## What happened
#64 added `UPLOADS_DIR` to the always-required list, on the reasoning that its fallback of `/app/uploads` is correct inside the container and wrong everywhere else, so nothing should inherit it silently. That reasoning stands.
What was missed is that **`docker-compose.qa.yml` never set it either.** QA had always relied on exactly the fallback #64 set out to stop people relying on. So the first deploy after #64 merged is the first one that fails.
This was an incomplete check on my part, and specifically a check that looked confident. #64's verification confirmed both CI workflows set every always-required variable, and reported that — but CI is not what deploys. The compose file, which is, was never looked at.
## The second half: setting it in the Portainer stack does not help
`ADMIN_GATE_SECRET` was added to the stack's environment variables and the container still reports it unset. That is not a mistake — it is how compose works, and it is the same mechanism #87 already documented:
> Compose only forwards variables explicitly declared in `environment:`.
Portainer's stack variables are available for **interpolation into the compose file** as `${VAR}`. They are not handed to the container. A service receives exactly what its `environment:` block lists, and nothing else.
So both variables need a line in the compose file, and they need different treatment:
| Variable | Line | Why |
| --- | --- | --- |
| `UPLOADS_DIR` | `UPLOADS_DIR=/app/uploads` | Not a secret, and it must match the right-hand side of the volume mapping. Hardcoded so it cannot drift from the mount. |
| `ADMIN_GATE_SECRET` | `ADMIN_GATE_SECRET=${ADMIN_GATE_SECRET}` | A secret, so the value stays in the stack environment and only the reference lives in the repo. |
The header comment listing required stack variables needs `ADMIN_GATE_SECRET` added too, since it is now one of them.
## Production is exposed to the same failure
Production runs from a Portainer stack outside this repository and has the same history — it will have been relying on the `/app/uploads` fallback as well. **It will refuse to boot on its next rebuild** unless `UPLOADS_DIR` is set there first. That is not fixed by this change, and it should be done before the next production deploy rather than discovered during one.
## The wider lesson worth recording
#64's checks answered "does CI still pass" when the question that mattered was "will this still deploy". Those are different questions, and a required-configuration change is precisely the case where the second one is the one to ask. Any future change to the always-required list should be checked against `docker-compose.qa.yml` and the production stack, not just the workflows.
## Verification
`docker compose -f docker-compose.qa.yml config` renders both variables with the expected values, and the QA container boots with no `[config] refusing to start` line — and, since `ADMIN_GATE_SECRET` will now be present, without the admin-gate warning either.
## Severity
High while it stands. QA is down and production is one rebuild away from the same.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The QA container refuses to boot:
What happened
#64 added
UPLOADS_DIRto the always-required list, on the reasoning that its fallback of/app/uploadsis correct inside the container and wrong everywhere else, so nothing should inherit it silently. That reasoning stands.What was missed is that
docker-compose.qa.ymlnever set it either. QA had always relied on exactly the fallback #64 set out to stop people relying on. So the first deploy after #64 merged is the first one that fails.This was an incomplete check on my part, and specifically a check that looked confident. #64's verification confirmed both CI workflows set every always-required variable, and reported that — but CI is not what deploys. The compose file, which is, was never looked at.
The second half: setting it in the Portainer stack does not help
ADMIN_GATE_SECRETwas added to the stack's environment variables and the container still reports it unset. That is not a mistake — it is how compose works, and it is the same mechanism #87 already documented:Portainer's stack variables are available for interpolation into the compose file as
${VAR}. They are not handed to the container. A service receives exactly what itsenvironment:block lists, and nothing else.So both variables need a line in the compose file, and they need different treatment:
UPLOADS_DIRUPLOADS_DIR=/app/uploadsADMIN_GATE_SECRETADMIN_GATE_SECRET=${ADMIN_GATE_SECRET}The header comment listing required stack variables needs
ADMIN_GATE_SECRETadded too, since it is now one of them.Production is exposed to the same failure
Production runs from a Portainer stack outside this repository and has the same history — it will have been relying on the
/app/uploadsfallback as well. It will refuse to boot on its next rebuild unlessUPLOADS_DIRis set there first. That is not fixed by this change, and it should be done before the next production deploy rather than discovered during one.The wider lesson worth recording
#64's checks answered "does CI still pass" when the question that mattered was "will this still deploy". Those are different questions, and a required-configuration change is precisely the case where the second one is the one to ask. Any future change to the always-required list should be checked against
docker-compose.qa.ymland the production stack, not just the workflows.Verification
docker compose -f docker-compose.qa.yml configrenders both variables with the expected values, and the QA container boots with no[config] refusing to startline — and, sinceADMIN_GATE_SECRETwill now be present, without the admin-gate warning either.Severity
High while it stands. QA is down and production is one rebuild away from the same.