Two different mechanisms both involve "environment variables" in Compose. Mixing them up is the most common Compose configuration bug.
.env compose.yaml container (web)
┌───────────────────────┐ ┌─────────────────────────────────────┐ ┌─────────────────┐
│ WEB_PORT=8000 │──►│ ports: "${WEB_PORT}:5000" │ │ │
│ POSTGRES_PASSWORD=... │──►│ environment: │──►│ DB_PASSWORD=... │
│ │ │ DB_PASSWORD: ${POSTGRES_PASSWORD} │ │ (no WEB_PORT) │
└───────────────────────┘ └─────────────────────────────────────┘ └─────────────────┘
1. interpolation: docker compose fills ${...} 2. environment: / env_file:
from the shell and .env, before parsing set the container's variablesInterpolation: .env and ${VAR}
Before Compose parses compose.yaml, it replaces every ${VAR} with a value. It looks in two places:
| Priority | Source |
|---|---|
| 1 (wins) | variables exported in your shell |
| 2 | the .env file next to compose.yaml |
The forms:
| Write | Result |
|---|---|
${VAR} | the value; empty string plus a warning if unset |
${VAR:-default} | default if unset or empty |
${VAR:?message} | error with message if unset or empty |
$$ | a literal $ |
.env uses KEY=value lines. It is not a shell script: no export needed, no expansion.
# .env
POSTGRES_PASSWORD=s3cret-workshop-pw
WEB_PORT=8000Container environment: environment and env_file
Only these two keys set variables inside a container. To give the app a value from .env, substitute it into environment::
services:
web:
build: .
ports:
- "${WEB_PORT}:5000"
environment:
DB_PASSWORD: ${POSTGRES_PASSWORD}
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}The password now lives in .env, which you keep out of Git (.gitignore). compose.yaml can be committed.
See both sides
docker compose config shows the file after interpolation:
web:
environment:
DB_PASSWORD: s3cret-workshop-pw
ports:
- mode: ingress
target: 5000
published: "8000"
protocol: tcpdocker compose exec web env shows what the container received: DB_PASSWORD is there, WEB_PORT is not.
An unset variable is not an error unless you use :?. Compose warns and carries on:
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.Applying a change
Interpolation happens every time a Compose command reads the file. After editing .env, run docker compose up -d; Compose recreates the services whose resolved configuration changed.