How to evaluate a dynamic variable in a docker-compose.yml file?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Hardcoding values like database passwords, port numbers, and image tags directly into a docker-compose.yml file creates a brittle configuration that breaks the moment you move between environments. Dynamic variables solve this problem by letting you inject values at runtime, so the same Compose file works in development, staging, and production without any edits. Understanding how Docker Compose evaluates these variables is essential for building maintainable, multi-container applications.
The Basic ${VAR} Syntax
Docker Compose uses the ${VAR} syntax to substitute environment variables into your configuration. When Compose reads the YAML file, it replaces every ${VAR} reference with the corresponding value from the environment before parsing the rest of the configuration.
In this example, APP_VERSION, HOST_PORT, and DATABASE_URL are all resolved from whatever environment is available when you run docker compose up. If a referenced variable is not set and has no default, Compose substitutes an empty string.
Using a .env File
Rather than exporting variables in your shell every time, Docker Compose automatically reads a file named .env in the same directory as your docker-compose.yml. This file uses a simple KEY=VALUE format.
Compose loads these values before processing the YAML file. This makes .env the ideal place to store per-environment defaults that developers can override locally without modifying tracked files. Add .env to your .gitignore if it contains secrets, and commit a .env.example with placeholder values instead.
Default Values and Required Variables
Docker Compose supports two forms of default value syntax that prevent empty-string substitutions from silently breaking your services.
${VAR:-default}substitutesdefaultifVARis unset or empty.${VAR-default}substitutesdefaultonly ifVARis unset (an empty string is kept).${VAR:?error message}causes Compose to abort with the error message ifVARis unset or empty. This is invaluable for required secrets that must never be left blank.
Shell Environment Variables Override .env
A common source of confusion is the precedence order. Shell environment variables always take priority over values in the .env file. The full precedence, from highest to lowest, is:
- Variables set in the shell (
export APP_VERSION=3.0.0) - Values in the
.envfile - Default values specified with
:-syntax in the YAML
This means a CI/CD pipeline can override .env defaults simply by exporting variables before running docker compose up, without touching any files.
Using Multiple .env Files
Starting with Docker Compose v2, you can specify multiple environment files using the env_file directive or the --env-file flag.
You can also specify a project-level env file from the command line:
When multiple files define the same variable, the last one wins. This pattern lets you layer a shared base configuration with service-specific or environment-specific overrides.
Validating Variable Substitution
Before deploying, always verify that your variables resolve correctly by running:
This command outputs the fully resolved Compose file with all variables substituted. It immediately reveals any unset variables, syntax errors, or unexpected empty values. Make this a standard step in your CI pipeline to catch configuration problems before they reach production.
Interpolation Inside environment vs env_file
There is an important distinction between the environment key and the env_file key. Variables referenced under environment in the YAML are interpolated by Compose using the shell and .env values. However, variables inside a file referenced by env_file are passed directly to the container without Compose-level interpolation.
This distinction matters when you need variable interpolation inside your environment values versus when you want literal strings passed through untouched.
Common Pitfalls
- Forgetting quotes around port mappings: YAML interprets
80:80as a base-60 number. Always quote port strings like"8080:3000". - Using
$VARinstead of${VAR}: While $VARsometimes works,${VAR}is the reliable form and is required when the variable name is adjacent to other text (e.g., ${APP}_service). - Committing
.envfiles with secrets: The.envfile is meant for local overrides. Use secret management tools for production credentials. - Assuming
env_filevalues are interpolated: They are not. Onlyenvironmententries in the YAML undergo Compose-level substitution. - Not running
docker compose config: Skipping validation leads to silent failures where empty strings replace missing variables.
Summary
- Use
${VAR}syntax indocker-compose.ymlto reference dynamic variables that are resolved at runtime. - Place default values in a
.envfile alongside your Compose file for automatic loading. - Use
${VAR:-default}for optional variables and ${VAR:?error}for required ones. - Shell environment variables override
.envvalues, giving CI/CD pipelines easy control. - Use multiple
env_fileentries to layer shared and service-specific configurations. - Always run
docker compose configto validate that all variables resolve correctly before deployment.

