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_DIR if 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