Docker
docker-compose
dynamic variables
yml file
DevOps

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.

yaml
1services:
2  web:
3    image: myapp:${APP_VERSION}
4    ports:
5      - "${HOST_PORT}:3000"
6    environment:
7      - DATABASE_URL=${DATABASE_URL}

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.

bash
1# .env
2APP_VERSION=2.1.0
3HOST_PORT=8080
4DATABASE_URL=postgres://user:pass@db:5432/mydb

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.

yaml
1services:
2  app:
3    image: myapp:${APP_VERSION:-latest}
4    environment:
5      - LOG_LEVEL=${LOG_LEVEL:-info}
6      - SECRET_KEY=${SECRET_KEY:?SECRET_KEY must be set}
  • ${VAR:-default} substitutes default if VAR is unset or empty.
  • ${VAR-default} substitutes default only if VAR is unset (an empty string is kept).
  • ${VAR:?error message} causes Compose to abort with the error message if VAR is 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:

  1. Variables set in the shell (export APP_VERSION=3.0.0)
  2. Values in the .env file
  3. 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.

yaml
1services:
2  api:
3    env_file:
4      - ./common.env
5      - ./api.env

You can also specify a project-level env file from the command line:

bash
docker compose --env-file ./production.env up -d

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:

bash
docker compose config

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.

yaml
1services:
2  worker:
3    environment:
4      # Compose resolves ${DB_HOST} before starting the container
5      - DATABASE_URL=postgres://user:pass@${DB_HOST}:5432/mydb
6    env_file:
7      # Values in worker.env are passed as-is to the container
8      - worker.env

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:80 as a base-60 number. Always quote port strings like "8080:3000".
  • Using $VAR instead of ${VAR}: While $VAR sometimes works, ${VAR} is the reliable form and is required when the variable name is adjacent to other text (e.g., ${APP}_service).
  • Committing .env files with secrets: The .env file is meant for local overrides. Use secret management tools for production credentials.
  • Assuming env_file values are interpolated: They are not. Only environment entries 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 in docker-compose.yml to reference dynamic variables that are resolved at runtime.
  • Place default values in a .env file alongside your Compose file for automatic loading.
  • Use ${VAR:-default} for optional variables and ${VAR:?error} for required ones.
  • Shell environment variables override .env values, giving CI/CD pipelines easy control.
  • Use multiple env_file entries to layer shared and service-specific configurations.
  • Always run docker compose config to validate that all variables resolve correctly before deployment.

Course illustration
Course illustration

All Rights Reserved.