env.dev

Docker Compose Environment Variables: The Complete Guide

How to use environment variables in Docker Compose: .env files, env_file directive, --env-file flag, multiple environments, variable substitution, and secrets management.

MethodSyntaxScope
Inline in YAMLenvironment: - KEY=valueSingle service
.env file (auto-loaded)Create .env next to docker-compose.ymlVariable substitution in YAML
env_file directiveenv_file: - ./app.envInjected into container
--env-file CLI flagdocker compose --env-file .env.prod upVariable substitution in YAML

Inline Environment Variables

The simplest way to set environment variables in Docker Compose is directly in your docker-compose.yml:

services:
  app:
    image: node:20
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=postgres://db:5432/myapp

You can also use the map syntax (without the dash):

services:
  app:
    image: node:20
    environment:
      NODE_ENV: production
      PORT: "3000"

Both formats are equivalent. The list syntax (- KEY=value) is more common in the wild.

Using .env Files with Docker Compose

Docker Compose automatically loads a file named .env in the same directory as your docker-compose.yml. Variables from this file are available for substitution in the YAML file itself — they are NOT automatically injected into containers.

Create a .env file:

# .env
POSTGRES_VERSION=16
APP_PORT=3000

Reference these variables in docker-compose.yml with ${} syntax:

services:
  db:
    image: postgres:${POSTGRES_VERSION}
    ports:
      - "${APP_PORT}:5432"

Important distinction: The .env file is for Compose file interpolation. To inject variables into the container's environment, use the env_file directive (next section).

The env_file Directive

To inject environment variables directly into a container, use env_file:

services:
  app:
    image: node:20
    env_file:
      - ./app.env
      - ./secrets.env

Where app.env contains:

# app.env
NODE_ENV=production
PORT=3000
API_KEY=sk-abc123

Key behaviors:

  • Variables are injected into the container's environment (visible via docker exec <container> env)
  • Later files override earlier ones if the same variable appears
  • Lines starting with # are comments
  • Empty lines are ignored
  • No quotes needed around values (quotes become part of the value)

The --env-file CLI Flag

The --env-file flag replaces the default .env file for Compose file interpolation:

# Use .env.prod instead of .env for variable substitution
docker compose --env-file .env.prod up

# Combine with other flags
docker compose --env-file .env.staging up -d

This flag affects which file is used for ${} substitution in docker-compose.yml — it does NOT change what gets injected into containers via env_file.

Multiple .env Files for Different Environments

A common pattern is maintaining separate env files per environment:

project/
├── docker-compose.yml
├── .env                 # Default/development values (auto-loaded)
├── .env.staging         # Staging overrides
├── .env.production      # Production overrides
├── app.env              # App-specific vars (injected into container)
└── db.env               # Database vars (injected into container)
# docker-compose.yml
services:
  app:
    image: myapp:latest
    env_file:
      - ./app.env
    environment:
      - APP_ENV=${APP_ENV:-development}

  db:
    image: postgres:${POSTGRES_VERSION:-16}
    env_file:
      - ./db.env

Run with different environments:

# Development (uses .env automatically)
docker compose up

# Staging
docker compose --env-file .env.staging up

# Production
docker compose --env-file .env.production up -d

Per-Service Environment Configuration

Each service can have its own combination of inline variables and env files:

services:
  frontend:
    image: nginx:alpine
    environment:
      - NGINX_PORT=80

  backend:
    image: node:20
    env_file:
      - ./backend.env
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://db:5432/app

  db:
    image: postgres:16
    env_file:
      - ./db.env
    environment:
      - POSTGRES_DB=myapp

Priority order (highest wins):

  1. environment values in docker-compose.yml
  2. Shell environment variables on the host
  3. env_file values
  4. Dockerfile ENV defaults

Variable Substitution and Interpolation

Docker Compose supports shell-like variable substitution in YAML:

services:
  app:
    image: myapp:${TAG:-latest}
    ports:
      - "${PORT:?PORT must be set}:3000"
    environment:
      - DEBUG=${DEBUG:-false}
SyntaxBehavior
${VAR}Value of VAR, empty if unset
${VAR:-default}Default if VAR is unset or empty
${VAR-default}Default only if VAR is unset
${VAR:?error}Error message if VAR is unset or empty
${VAR?error}Error message only if VAR is unset

Check what Compose will resolve:

docker compose config

This prints the fully resolved docker-compose.yml with all variables substituted.

Secrets Management Best Practices

Never commit secrets to version control. Here's a safe pattern:

# .env.example (commit this — documents required variables)
DATABASE_URL=
API_KEY=
JWT_SECRET=

# .env (DO NOT commit — add to .gitignore)
DATABASE_URL=postgres://user:pass@db:5432/app
API_KEY=sk-live-abc123
JWT_SECRET=super-secret-key

Add to .gitignore:

.env
.env.production
.env.staging
*.env
!.env.example

For production, consider Docker secrets:

services:
  app:
    image: myapp:latest
    secrets:
      - db_password
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Common Pitfalls and Debugging

Quotes become part of the value:
# WRONG — value will be "production" (with quotes)
NODE_ENV="production"

# RIGHT — value will be production
NODE_ENV=production
.env vs env_file confusion:
  • .env (auto-loaded) → substitution in docker-compose.yml only
  • env_file directive → injected into container environment
Variables not updating:
# Recreate containers to pick up env changes
docker compose up -d --force-recreate

# Or rebuild if using build-time args
docker compose up -d --build
Debugging current values:
# See resolved compose file
docker compose config

# See container environment
docker exec <container> env

# See what .env file is loaded
docker compose config --environment

FAQ

Can I use multiple .env files at once?

Not with the auto-loaded .env. Docker Compose only auto-loads one .env file. Use --env-file to specify a different one, or use the env_file directive in YAML for container-level env files (which does support multiple files).

What's the difference between environment and env_file?

environment sets variables inline in the YAML. env_file loads variables from a file. Both inject into the container. environment values take priority over env_file values.

Do I need docker-compose.yml or compose.yml?

Both work. Docker Compose V2 (the docker compose command) prefers compose.yml but supports both. Use whichever your team prefers.

How do I pass host environment variables to a container?

List the variable name without a value:

environment:
  - MY_HOST_VAR

This passes the host's MY_HOST_VAR value into the container.

Does docker compose --env-file affect env_file in the YAML?

No. --env-file only affects ${} substitution in the Compose file. The env_file directive in YAML independently loads its specified files into the container.