WARN[0000] The "DB_PASSWORD" variable is not set. Defaulting to a blank string.
Compose prints that, then starts the stack anyway. The database exits, or the app connects with an empty password, and you only find the yellow line by scrolling back up. The frustrating part: DB_PASSWORD is right there in app.env, which the service loads with env_file:.
Two stages, two sets of files
Interpolation happens on your machine when Compose reads the YAML. ${VAR} and $VAR are filled from your shell, --env-file, or the project .env next to the compose file. Nothing else. If a variable is missing and has no default, it becomes "" with a warning.
Container environment comes later. env_file: and environment: decide what the process inside the container sees. app.env is read here, so it can never fill a ${...} in the compose file.
Two more traps from the same root cause:
-
environment: [DEBUG]with nothing set on the host is silently dropped, no warning. -
command: sh -c 'echo $CACHE_DIR'is interpolated from your laptop while the file loads. Quotes are just YAML text. Write$$CACHE_DIRif the container should expand it.
The request to make this fail by default is years old: docker/compose#5505.
The fix
# require it: Compose stops with an error instead of a blank
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
Put interpolation variables in the project .env, escape container-side ones as $$, and never commit ${DB_PASSWORD:-hunter2}. docker compose config shows the result, but only when you run it, and it prints secrets in clear text.
Catch it in the editor
I built ComposeVars, a free VS Code extension that reads your compose files and the env files Compose would read (.env, include: env files, and --env-file / COMPOSE_ENV_FILES found in package.json, Makefile or justfile). Six rules, CV001 to CV006: unset variables, .env keys nothing uses, pass-through env that is not set, missing env_file paths, host-side $VAR in commands, and committed secret defaults. Quick Fixes add :?, :-, $$ or an .env.example entry, and hovering a variable shows where its value comes from.
Secret-looking values are always masked, files outside the workspace are never read, and there is no network or telemetry. It cannot see your shell, so a variable you always export needs to go in composeVars.ignoreVariables.
It gets its own activity bar icon with a badge, and a Findings panel listing each problem by variable name, with inline Quick Fixes.
code --install-extension jaytankdev.composevars
Source (MIT): github.com/jay-tank/composevars
Full write-up (every rule, the interpolation syntax, limitations, FAQ): The "DB_PASSWORD" variable is not set. Defaulting to a blank string
Top comments (2)
The two-stage split is the whole explanation, and it deserves stating as bluntly as possible: env_file can't fill ${...} because they're read at different times by different processes. People lose hours to this because both get called "environment variables."
${VAR:?} is the right fix, and CI is where it earns its keep , a blank POSTGRES_PASSWORD in a pipeline usually produces a green run and a broken deploy rather than an obvious failure.
The $$ escape reads like a typo to everyone the first time, so it deserves a comment in the file explaining why it's there.
Hay là dùng cú pháp
${VAR?error message}trong compose file để fail fast ngay lúcdocker compose upthay vì để nó default sang empty string rồi debug sau. VD:DB_PASSWORD=${DB_PASSWORD?Missing DB_PASSWORD in .env}. Kết hợp vớidocker compose config --quiettrong CI để bắt sớm trước khi deploy PS: the tool I meant is on labagent .tech