Docker Compose Environment Variables Explained
Docker Compose has four different-looking things that all involve "environment variables," and they get conflated constantly: the .env file, ${VAR-default} interpolation, the environment: key, and env_file:. They solve different problems and only some of them actually reach your application code.
1. The .env file — read by the Compose CLI, not your app
A .env file sitting next to compose.yml is read automatically by the docker compose command itself, to fill in ${VARIABLE} placeholders inside compose.yml. It is not automatically injected into any container's runtime environment.
# .env MYSQL_PASSWORD=secret
# compose.yml
services:
mysql:
image: mysql:8
environment:
MYSQL_PASSWORD: ${MYSQL_PASSWORD-secret}Here, .env supplies the value that gets substituted into ${MYSQL_PASSWORD-secret} when Compose parses the file — and separately, the resulting value is passed into the container via environment:. Two different mechanisms, chained together.
2. ${VAR-default} interpolation: mind the dash vs colon-dash
Every generated file on this site uses the ${VAR-default} form deliberately. Compose supports two variants that look almost identical but behave differently:
${VAR-default}— usesdefaultonly ifVARis completely unset. An empty string still counts as "set."${VAR:-default}— usesdefaultifVARis unset or set to an empty string.
For optional settings like ports, this distinction rarely bites. For anything where an accidentally-empty .env line should still fall back to a sane default, the colon form is the safer choice.
3. environment: — sets variables inside a specific container
The environment: key defines variables that exist inside that container's process at runtime — readable by your application via getenv(), $_ENV, or the framework's own config layer. This is what your PHP code actually sees.
services:
php:
image: php:8.5-fpm
environment:
APP_ENV: dev
REDIS_URL: redis://redis:63794. env_file: — load many variables from a file at once
Instead of listing every variable under environment:, env_file: loads an entire KEY=VALUE file straight into the container's environment:
services:
php:
image: php:8.5-fpm
env_file:
- .envNote the overlap with point 1: nothing stops you from pointing env_file: at the same .env that Compose also uses for interpolation — but they remain two separate reads of that file, for two separate purposes.
Precedence: environment: wins
If a variable is defined both via env_file: and directly under environment: for the same service, the value in environment: takes precedence. This is a common source of "why isn't my .env change taking effect" — a hardcoded environment: entry is silently overriding it.
The practical rule of thumb
Use .env plus ${VAR-default} interpolation for anything that configures compose.yml itself — host ports, image tags, volume paths. Use environment: or env_file: for values your application inside the container actually needs to read. Every stack generated on this site follows exactly this split.
Generate a ready-to-run Docker Compose setup for your stack — compose.yml, nginx config, .env, and a README, in seconds.
Open the generator →