Compose build secret sourced from `secrets.<name>.environment` mounts EMPTY on first deploy (UI env var not injected into build), silently breaking private-registry installs
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 37.4k
- Forks
- 3k
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 73
Description
Summary
When a Docker Compose stack defines a build-time BuildKit secret sourced from an environment variable:
secrets:
my_token:
environment: MY_TOKEN
and MY_TOKEN is set in the Dokploy Environment tab, the secret file mounted into the build (/run/secrets/my_token) can be empty — even though the variable is configured. It appears to be a timing/propagation race: the first deploy after setting/creating the stack builds with an empty secret, and an unchanged redeploy then succeeds.
Because the secret is only empty (not missing), builds don't fail with an obvious "secret not set" error — they proceed with a blank value, which surfaces as a confusing downstream failure (in our case an HTTP 404 from a private npm registry, because an empty auth token is treated as an anonymous request).
Environment
- Dokploy version: 0.30.0
- Deploy type: Docker Compose (compose file with
build:+ top-levelsecrets:) - Server OS: Ubuntu 24.04 LTS (Noble)
- Docker: 29.6.1 (BuildKit)
How environment variables are documented to flow
Per the Docker Compose docs page, variables from the Environment tab are written to a .env file next to the compose file and are used for ${VAR} interpolation; they are not automatically injected into containers or the build.
The Compose secrets.<name>.environment: MY_TOKEN form does not interpolate — it looks MY_TOKEN up in the process environment of the docker compose build invocation. When Dokploy has not (yet) exported the UI variable into that process environment, the secret resolves to an empty string. This matches the behavior reported in #726, #1244, #2777, and #2473.
Reproduction
-
Create a Docker Compose stack in Dokploy whose build needs a secret at build time. Minimal Dockerfile:
FROM alpine RUN --mount=type=secret,id=my_token \ echo "secret bytes: $(wc -c < /run/secrets/my_token)" -
docker-compose.yml:services: app: build: context: . secrets: - my_token image: repro:latest secrets: my_token: environment: MY_TOKEN -
In the Dokploy Environment tab set
MY_TOKEN=hello-world. -
Deploy for the first time. Observe the build log.
Expected
secret bytes: 12 (or 11) — the secret contains the configured value on the first deploy.
Actual
secret bytes: 0 — the secret is empty on the first deploy. Running an unchanged redeploy then prints the correct non-zero byte count. The value was configured the whole time; only the first build saw it empty.
Real-world impact
We hit this installing private packages from a GitLab npm registry during pnpm install --frozen-lockfile in a multi-stage build. The token is passed as a build secret:
RUN --mount=type=secret,id=gitlab_npm_token \
GITLAB_NPM_TOKEN="$(cat /run/secrets/gitlab_npm_token)" pnpm install --frozen-lockfile
With the secret empty, the registry auth header became Authorization: Bearer (empty), and GitLab returns 404 (not 401) for private packages accessed anonymously:
ERR_PNPM_FETCH_404 GET https://gitlab.com/api/v4/projects/.../@scope/pkg-2.3.0.tgz: Not Found - 404
An authorization header was used: Bearer [hidden]
This is especially hard to debug because:
- The variable is set in the UI, so it looks correct.
- The error is a downstream 404, not a "secret missing" error.
- An identical redeploy fixes it, making it look intermittent/environmental.
We confirmed via a direct byte-count probe (wc -c < /run/secrets/gitlab_npm_token) that the secret was empty on the failing build and populated after the redeploy — the token, package, and lockfile were all valid throughout.
Expected fix / request
One or more of:
- Ensure UI Environment variables are exported into the build process environment so
secrets.<name>.environment: VARresolves them on the first deploy, deterministically (no redeploy race). - If a build secret sourced from
environment:resolves empty, surface a warning/error in the build log ("secretmy_tokensourced fromMY_TOKENis empty") instead of silently mounting a blank file. - Document, on the Docker Compose page, that
secrets.<name>.environmentreads the process env (not the interpolated.env), and show the supported pattern for build-time secrets from UI variables.
Related
- #726 — Compose service ignores environment variables
- #1244 — Environment variables not set during build with a Dockerfile
- #2777 — Environment variables not loaded in Docker Compose (raw mode), default to blank
- #2473 —
.envnot generating
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the provided docker-compose.yml and Dockerfile reproduction, then trace how Dokploy's Environment-tab values reach the docker compose build process on the first deploy. Verify the /run/secrets/my_token byte count before and after an unchanged redeploy; done means the first deploy receives the configured value deterministically or clearly reports an empty secret.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, docker-compose, typescript
- Domain
- build-system, devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100