Dokploy / Dokploy/dokploy

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

Open
#5,167 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

awaiting-feedback
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-level secrets:)
  • 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

  1. 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)"
    
  2. docker-compose.yml:

    services:
      app:
        build:
          context: .
          secrets:
            - my_token
        image: repro:latest
    secrets:
      my_token:
        environment: MY_TOKEN
    
  3. In the Dokploy Environment tab set MY_TOKEN=hello-world.

  4. 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:

  1. Ensure UI Environment variables are exported into the build process environment so secrets.<name>.environment: VAR resolves them on the first deploy, deterministically (no redeploy race).
  2. If a build secret sourced from environment: resolves empty, surface a warning/error in the build log ("secret my_token sourced from MY_TOKEN is empty") instead of silently mounting a blank file.
  3. Document, on the Docker Compose page, that secrets.<name>.environment reads 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 — .env not generating

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.