Docker Compose offers five different ways to set environment variables, and the Docker documentation splits them across four separate pages. This guide consolidates everything in one place with copy-paste examples for each method and a clear explanation of which takes priority when they conflict.
Method 1: Inline in docker-compose.yml
The simplest approach — set values directly under environment::
# docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret123When to use: Non-sensitive config that is the same in all environments (e.g., app port, log level). Avoid secrets here — they end up in version control.
Method 2: The .env File (Variable Substitution)
Docker Compose automatically reads a .env file in the same directory as docker-compose.yml. Variables in this file can be substituted into the Compose file using ${VAR} syntax.
# .env ← Docker Compose reads this automatically POSTGRES_PASSWORD=secret123 API_KEY=abc-def-456 APP_PORT=3000
# docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # substituted from .env
app:
image: myapp:latest
ports:
- "${APP_PORT}:3000"
environment:
API_KEY: ${API_KEY}Important: This only substitutes variables in the Compose file. The container itself only sees the variables you explicitly list under environment:. To load all .env variables into the container, use Method 3 below.
Method 3: env_file Attribute
Use env_file: to load all KEY=VALUE pairs from a file directly into the container environment:
# app.env NODE_ENV=production DATABASE_URL=postgres://admin:secret@db:5432/myapp REDIS_URL=redis://redis:6379 LOG_LEVEL=info
# docker-compose.yml
services:
app:
image: myapp:latest
env_file:
- app.env # all variables loaded into container
- secrets.env # you can specify multiple filesTip: Use env_file for long lists of variables. Use environment:to override specific values on top of what env_file provides.
Method 4: Default Values and Required Variables
environment:
# Use value if set, fall back to "localhost" if not
DB_HOST: ${DB_HOST:-localhost}
# Error and stop if variable is not set
API_SECRET: ${API_SECRET:?Error: API_SECRET must be set}
# Use value if set; fall back to empty string
OPTIONAL_FLAG: ${OPTIONAL_FLAG-}Syntax summary:
| Syntax | Behavior |
|---|---|
| ${VAR} | Use VAR; empty string if not set |
| ${VAR:-default} | Use VAR if set and non-empty; else use "default" |
| ${VAR-default} | Use VAR if set (even if empty); else use "default" |
| ${VAR:?error} | Use VAR; print error message and stop if not set |
| ${VAR:+value} | Use "value" if VAR is set; else empty string |
Method 5: Command-Line Override
# Override a single variable for one run docker compose run -e NODE_ENV=staging app # Override multiple variables docker compose run -e NODE_ENV=staging -e DEBUG=true app # Pass current shell variable into container export MY_VAR=hello docker compose run -e MY_VAR app
Command-line values have the highest priority — they override everything else.
Precedence Order (Highest to Lowest)
| Priority | Source | Example |
|---|---|---|
| 1 (highest) | docker compose run -e | docker compose run -e VAR=val |
| 2 | Shell environment interpolated in Compose file | export VAR=val → ${VAR} in yml |
| 3 | environment: key in docker-compose.yml | environment:\n VAR: value |
| 4 | env_file: key in docker-compose.yml | env_file: - app.env |
| 5 (lowest) | ENV in Dockerfile | ENV VAR=default |
Real-World Example: Node.js + PostgreSQL + Redis
# .env (gitignored — contains real secrets)
POSTGRES_PASSWORD=super_secret_pw
REDIS_PASSWORD=redis_secret
JWT_SECRET=my_jwt_secret_key
# .env.example (committed — shows teammates required vars)
POSTGRES_PASSWORD=your_postgres_password
REDIS_PASSWORD=your_redis_password
JWT_SECRET=your_jwt_secret
# docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # from .env
redis:
image: redis:7
command: redis-server --requirepass ${REDIS_PASSWORD}
app:
image: myapp:latest
env_file:
- .env # loads all vars into container
environment:
NODE_ENV: production # override specific vars inline
DATABASE_URL: postgres://admin:${POSTGRES_PASSWORD}@db:5432/myapp
depends_on:
- db
- redisSecurity Best Practices
- Never commit .env to version control — add it to
.gitignore. Do commit.env.examplewith placeholder values. - Use Docker Secrets in production — for Swarm or Kubernetes, use native secret management rather than environment variables, which can be exposed in process listings.
- Use
:?syntax for required vars —${API_KEY:?API_KEY is required}makes missing secrets a startup error, not a silent bug. - Validate env vars at startup — have your application check all required variables exist and log a clear error if any are missing.