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} — uses default only if VAR is completely unset. An empty string still counts as "set."
  • ${VAR:-default} — uses default if VAR is 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:6379

4. 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:
      - .env

Note 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.

Try it yourself

Generate a ready-to-run Docker Compose setup for your stack — compose.yml, nginx config, .env, and a README, in seconds.

Open the generator →