Skip to content
Latchkey

Docker Compose Env Interpolation - Empty Default Yields Wrong Values in CI

Compose interpolates ${VAR} from the environment and .env. When a variable is unset it defaults to an empty string (with a warning), which can silently produce a wrong image tag or config. The :-, -, and :? modifiers control defaults and required-variable behavior.

What this error means

A docker compose up warns The "TAG" variable is not set. Defaulting to a blank string and then uses an empty value (e.g. image: myorg/api::latest or invalid). Or a ${VAR:?msg} fails the run because a required variable is missing.

docker compose output
WARN[0000] The "TAG" variable is not set. Defaulting to a blank string.
# image: myorg/api:${TAG}  ->  myorg/api:   (empty tag)
# or required: error: required variable "DB_PASSWORD" is missing a value: set it in .env

Diagnose it: read the resolved config, not the file you wrote

Compose merges override files, interpolates variables, and applies defaults before it does anything. Most Compose failures in CI are visible in the resolved configuration and invisible in the source file, because the value you are debugging came from an unset variable that quietly became an empty string.

Terminal
# the fully merged, interpolated configuration Compose will actually run
docker compose config

# fail loudly on unset variables instead of silently interpolating empty
docker compose --env-file .env config --quiet || echo "invalid"

# which override files were picked up
docker compose config --services

Common causes

Unset variable defaults to empty

Compose substitutes an unset ${VAR} with an empty string and only warns. An empty tag or value flows through silently, producing a wrong reference or config.

Confusing :- vs - default syntax

${VAR:-default} uses the default when VAR is unset OR empty; ${VAR-default} only when VAR is unset (an empty VAR stays empty). Choosing the wrong one yields surprising values.

Required variable not provided

A ${VAR:?error message} is meant to fail loudly when VAR is missing. The failure is intentional - provide the value.

How to fix it

Provide explicit defaults with :-

Default required-but-optional variables so an unset value does not become empty.

docker-compose.yml
services:
  api:
    image: myorg/api:${TAG:-latest}   # default to latest if unset/empty

Make truly-required variables fail fast

Use :? so a missing required value stops the run with a clear message.

docker-compose.yml
environment:
  DB_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in the environment or .env}

Bind mounts behave differently on a runner

  • A relative bind source is resolved against the compose file location, not the working directory of the shell that invoked it.
  • The host path must exist before up. Compose creates missing directories for named volumes but not for bind mounts, and the failure surfaces as a mount error rather than a missing-path error.
  • On a CI runner the workspace path differs from your machine, so any absolute host path in a compose file is a portability bug waiting for its first CI run.
  • Prefer named volumes for anything that does not genuinely need to be read from the host. They remove the whole class of problem.

How to prevent it

  • Default optional variables with ${VAR:-default}.
  • Mark required variables with ${VAR:?message} to fail fast.
  • Run docker compose config to verify interpolated values before running.

Frequently asked questions

What causes Docker Compose env interpolation?
There are 3 common causes: unset variable defaults to empty, confusing :- vs - default syntax, and required variable not provided. Compose substitutes an unset ${VAR} with an empty string and only warns.
How do I fix Docker Compose env interpolation?
There are 2 fixes depending on which cause you have: provide explicit defaults with :- and make truly-required variables fail fast. Work through them in order, since the first is the most common.
What does Docker Compose env interpolation actually mean?
A docker compose up warns The "TAG" variable is not set.
How do I stop Docker Compose env interpolation happening again?
Default optional variables with ${VAR:-default}. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card