01Environment Variables Are More Confusing Than They Should Be
Docker Compose has at least four different ways to pass environment variables to containers, and the documentation doesn't always make it clear which one to use. I've debugged more "why isn't my env var showing up" issues than I care to admit — for myself and others.
The confusion stems from a subtle distinction: there are variables that Compose itself interpolates (used in the YAML file), and variables that get passed into the running container. These are two different mechanisms, and mixing them up is the number one source of env var bugs.
This guide covers every method, when to use each one, and the precedence rules that determine which value wins when the same variable is defined in multiple places.
02The .env File (Compose Interpolation)
The .env file in the same directory as your docker-compose.yml is special. Compose reads it automatically and uses its values for variable substitution in the YAML file itself. This happens before the containers start.
[.env]
1# .env (read by Compose for YAML interpolation)2POSTGRES_VERSION=163DB_PASSWORD=supersecret4APP_PORT=808003Using Variables in docker-compose.yml
Reference .env variables in your compose file with ${VAR} syntax:
[docker-compose.yml]
1services: 2 db: 3 image: postgres:${POSTGRES_VERSION:-16} # Default to 164 environment: 5 - POSTGRES_PASSWORD=${DB_PASSWORD}6 ports: 7 - "${APP_PORT}:5432"89 app: 10 image: myapp:latest11 env_file: 12 - ./app.env # Separate env file for app-specific vars13 environment: 14 - DATABASE_URL=postgres://user:${DB_PASSWORD}@db:5432/myapp15 - NODE_ENV=production # Hardcoded valueUse ${VAR:-default} syntax to provide fallback values. This prevents errors when a variable isn't set and makes your compose file self-documenting.
04Variable Precedence Rules
When the same variable is defined in multiple places, Compose follows this precedence (highest to lowest):
1. Shell environment variables (exported in your terminal)
2. Values from the .env file
3. Values from env_file directive files
4. Default values in the Compose file (${VAR:-default})
This means a shell export always wins over .env, which always wins over env_file. This is useful for overriding values in CI/CD pipelines without modifying files.
A common pattern I use: the .env file has development defaults, and production values are set via shell environment variables or a CI/CD system. The compose file works in both environments without changes.
05Docker Secrets for Sensitive Data
For passwords and API keys, Docker secrets are more secure than environment variables. Secrets are mounted as files inside the container rather than being visible in environment variable listings or docker inspect output:
[docker-compose.yml]
1services: 2 db: 3 image: postgres:16-alpine4 secrets: 5 - db_password6 environment: 7 POSTGRES_PASSWORD_FILE: /run/secrets/db_password89secrets: 10 db_password: 11 file: ./secrets/db_password.txtAlways add .env files and secrets/ directories to .gitignore. Leaked credentials in Git history are the most common cause of Docker container compromises.