Docker Compose Variable Interpolation#
Docker Compose supports Bash-like variable substitution in Compose files using $VARIABLE or ${VARIABLE} syntax. When a variable cannot be resolved and no default is provided, Compose does not error β it silently substitutes an empty string and emits a warning . This silent fallback is the root cause of several common security and runtime bugs in course exercises.
Interpolation Syntax#
The Compose Specification supports four substitution forms for braced expressions:
| Syntax | Behavior |
|---|---|
${VAR} | Direct substitution β value of VAR |
${VAR:-default} | Value of VAR if set and non-empty, else default |
${VAR-default} | Value of VAR if set (even if empty), else default |
${VAR:?error} | Value of VAR if set and non-empty, else exit with error |
${VAR?error} | Value of VAR if set (even if empty), else exit with error |
${VAR:+replacement} | replacement if VAR set and non-empty, else empty |
Interpolation applies to YAML values only, not to keys. For label/environment keys, use the list (- KEY=VALUE) form rather than the map form . Use $$ to escape a literal dollar sign and prevent interpolation .
Variable sources are resolved in this precedence order :
- Shell environment
- File specified by
--env-file .envfile in the project directory
The Silent Empty-String Trap#
If POSTGRES_VERSION is unset and you write image: "postgres:${POSTGRES_VERSION}", Compose resolves this to image: "postgres:" β an invalid image reference. Docker's own docs flag this as a likely error: postgres: is not a valid tag .
The same silent substitution applies to any field β including the user: directive that controls which OS user runs processes inside the container.
Security Risk: CURRENT_UID and Running as Root#
The course docker-compose.yml uses ${CURRENT_UID} to set the container process owner :
user: ${CURRENT_UID}
The .env file ships with:
CURRENT_UID=0 # Root by default
This means:
- If a student runs
docker compose upwithout overridingCURRENT_UID, the container runs as UID 0 (root), defeating the security intent of theuser:directive. - If
CURRENT_UIDis entirely unset (no.env, no shell export), Compose substitutes an empty string, which Docker interprets as the image's default user β often also root.
The course slides document this explicitly: the user: field is set to ${CURRENT_UID} so that files written to mounted volumes are owned by the host user rather than root. If the variable is missing, containers silently run as root .
Mitigations#
Use ${VAR:?error} for required variables. This causes docker compose config and docker compose up to fail fast with a descriptive message if the variable is unset or empty β instead of silently proceeding with a broken or insecure value .
Set CURRENT_UID explicitly before running Compose:
export CURRENT_UID=$(id -u)
docker compose up
Verify resolved values before starting containers:
docker compose config # Shows fully-resolved Compose file
docker compose config --environment # Shows all variables and their sources
Key References#
- Compose Specification: Interpolation β syntax reference and substitution rules
- How-to: Variable Interpolation β
.envfile format, precedence, and examples - Course
docker-compose.ymlβ anchor block showinguser: ${CURRENT_UID}pattern .envdefaults βCURRENT_UID=0shipped as the default- Course slides on
user:andCURRENT_UIDβ explanation of the security intent and failure mode