Skip to content
intermediate Phase 4 · Docker Compose

Environment & Secrets

Manage environment variables, .env files, and secrets in Compose.

45m
0 problems
Topic Progress 0%

Environment Variables in Compose

Environment Variables in Compose

Three Ways to Set Environment Variables

services:
  api:
    image: myapi

    # 1. Inline key=value
    environment:
      DB_HOST: db
      DB_PORT: 5432
      NODE_ENV: production

    # 2. List format (less readable)
    environment:
      - DB_HOST=db
      - DB_PORT=5432

    # 3. From .env file
    env_file:
      - .env
      - ./api/.env
      - path: ./secrets.env
        required: false

.env File

Place a .env file in the same directory as docker-compose.yml:

# .env
DB_PASSWORD=supersecret
API_KEY=abc123
REDIS_URL=redis://redis:6379
POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret

Variable Substitution

Compose supports ${VARIABLE} syntax in YAML files:

services:
  db:
    image: postgres:${POSTGRES_VERSION:-16}
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB:-myapp}

  api:
    image: myapi:${API_VERSION:-latest}
    environment:
      DATABASE_URL: postgresql://${DB_USER:-postgres}:${DB_PASSWORD}@db:5432/${POSTGRES_DB}

Syntax:

  • ${VARIABLE} — Use variable, error if missing
  • ${VARIABLE:-default} — Use variable, default if missing or empty
  • ${VARIABLE-default} — Use variable, default only if missing
  • ${VARIABLE:?error} — Use variable, error with message if missing
  • ${VARIABLE:?error message} — Custom error message

Environment Variable Precedence

From lowest to highest priority:

  1. Compose file environment values
  2. Shell environment variables (host)
  3. .env file in Compose directory
  4. env_file in Compose file
  5. environment in Compose file (overrides all)
# Shell variable overrides .env
MY_VAR=shell_value docker compose up -d

Multiple Environments

# Development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# docker-compose.dev.yml
services:
  api:
    build:
      target: development
    environment:
      NODE_ENV: development
    volumes:
      - ./src:/app/src

# docker-compose.prod.yml
services:
  api:
    build:
      target: production
    environment:
      NODE_ENV: production
    restart: always

Secrets and Configs

Secrets and Configs

Docker Secrets (Swarm Mode)

Secrets are encrypted at rest and only mounted in memory:

services:
  db:
    image: postgres:16
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

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

Configs (Compose V2)

Configs mount non-sensitive configuration files:

services:
  nginx:
    image: nginx:1.25
    configs:
      - source: nginx_conf
        target: /etc/nginx/nginx.conf
        mode: 0444

configs:
  nginx_conf:
    file: ./config/nginx.conf

Best Practices for Sensitive Data

Never do:

# BAD: Hardcoded secrets in docker-compose.yml
services:
  db:
    environment:
      POSTGRES_PASSWORD: supersecret123

Do:

# GOOD: Use .env file (not committed to git)
# docker-compose.yml
services:
  db:
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}

# .env (in .gitignore)
DB_PASSWORD=supersecret123
# BETTER: Use env_file with separate secrets file
services:
  db:
    env_file:
      - .env
      - path: .secrets.env
        required: false

.gitignore for Secrets

# .gitignore
.env
.env.local
.env.production
*.env
secrets/

Runtime Secrets (Docker Swarm)

# Create secret
echo "my_secret_password" | docker secret create db_pass -

# Use in compose
services:
  db:
    image: postgres:16
    secrets:
      - db_pass
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_pass

secrets:
  db_pass:
    external: true

Compose Configuration Reference

Compose Configuration Reference

Complete docker-compose.yml Example

services:
  web:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        NODE_ENV: production
    image: myapp/web:latest
    container_name: web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - static_files:/usr/share/nginx/html
    networks:
      - frontend
    depends_on:
      api:
        condition: service_healthy
    environment:
      - API_URL=http://api:4000
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: 256M
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.web.rule=Host(`example.com`)"

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    image: myapp/api:latest
    ports:
      - "4000:4000"
    networks:
      - frontend
      - backend
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    env_file:
      - .env
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s
    restart: unless-stopped
    stop_grace_period: 30s

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - backend
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-myapp}
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  pgdata:
  redis_data:
  static_files:

networks:
  frontend:
  backend:
    internal: true

Useful Commands

# Validate compose file
docker compose config

# Show resolved config
docker compose config --format json

# List services
docker compose ps

# View images used
docker compose images

# Watch for changes (Docker Compose v2.22+)
docker compose watch