How to evaluate a dynamic variable in a docker-compose.yml file?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
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.
Related reading
- How to exec into a container and view file if container is in CrashLoopBackOff state
- How to execute MySQL command from the host to container running MySQL server?
- How to exempt a directory when using readOnlyRootFilesystem in kubernetes?
- How to expose Kafka from Docker to the outside world?
- How to execute a sql script file in a Kubernetes Pod?
- How to explicitely define an Endpoint of an Kubernetes Service
- How to fix dial unix /var/run/docker.sock connect permission denied when group permissions seem correct?
- How to fix Docker, Got permission denied issue

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.