Docker Compose Environment Variables: Complete Guide with Examples

10 min readDevOps & Docker

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: secret123

When 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 files

Tip: 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:

SyntaxBehavior
${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)

PrioritySourceExample
1 (highest)docker compose run -edocker compose run -e VAR=val
2Shell environment interpolated in Compose fileexport VAR=val → ${VAR} in yml
3environment: key in docker-compose.ymlenvironment:\n VAR: value
4env_file: key in docker-compose.ymlenv_file: - app.env
5 (lowest)ENV in DockerfileENV 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
      - redis

Security Best Practices

  • Never commit .env to version control — add it to .gitignore. Do commit .env.example with 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.

Related Resources