docker.recipes
Fundamentals8 min read

Environment Variables Best Practices for Docker Compose

Master the art of managing environment variables in Docker Compose for security and flexibility.

01Introduction

Environment variables are how you configure Docker containers without hardcoding values. They're essential for secrets, configuration differences between environments, and keeping your compose files shareable. Let's explore the best practices.

02The .env File

Docker Compose automatically loads variables from a .env file in the same directory. This is the standard way to separate configuration from your compose file.
1# .env file - loaded automatically by Docker Compose
2
3# Database Configuration
4POSTGRES_USER=myapp
5POSTGRES_PASSWORD=super_secret_password_123
6POSTGRES_DB=myapp_production
7
8# Application Settings
9APP_PORT=8080
10APP_DEBUG=false
11APP_SECRET_KEY=another_random_secret
12
13# External Services
14SMTP_HOST=smtp.example.com
15SMTP_PORT=587
16SMTP_USER=notifications@example.com

03Using Variables in Compose Files

Reference .env variables using ${VARIABLE} syntax. You can also provide defaults for optional variables.
1services:
2 app:
3 image: myapp:${APP_VERSION:-latest} # Default to 'latest' if not set
4 ports:
5 - "${APP_PORT:-8080}:80"
6 environment:
7 - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db/${POSTGRES_DB}
8 - DEBUG=${APP_DEBUG:-false}
9 - SECRET_KEY=${APP_SECRET_KEY}
10
11 db:
12 image: postgres:15
13 environment:
14 POSTGRES_USER: ${POSTGRES_USER}
15 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
16 POSTGRES_DB: ${POSTGRES_DB}

Use ${VAR:-default} for optional variables with defaults. Use ${VAR:?error message} for required variables that should fail if missing.

04Always Include .env.example

Never commit .env to version control (it contains secrets). Instead, provide a .env.example template that documents all required variables.
1# .env.example - Template for environment configuration
2# Copy this file to .env and fill in the values
3
4# Database Configuration (required)
5POSTGRES_USER=myapp
6POSTGRES_PASSWORD=change_me_in_production
7POSTGRES_DB=myapp
8
9# Application Settings
10APP_PORT=8080
11APP_DEBUG=true
12APP_SECRET_KEY=generate_a_random_string_here
13
14# External Services (optional)
15# SMTP_HOST=smtp.example.com
16# SMTP_PORT=587

Add .env to .gitignore immediately when starting a project. Committed secrets are a common security incident.

05Multiple Environment Files

Use different .env files for different environments. The --env-file flag lets you specify which to use.
1# Project structure
2project/
3├── docker-compose.yml
4├── .env # Default (usually development)
5├── .env.production # Production values
6├── .env.staging # Staging values
7└── .env.example # Template
8
9# Run with specific environment
10docker compose --env-file .env.production up -d
11
12# Or set in shell before running
13export $(cat .env.production | xargs) && docker compose up -d

06Secrets vs Configuration

Not all environment variables are equal. Distinguish between secrets (passwords, API keys) and configuration (ports, feature flags). Secrets need extra protection.
1# Configuration: can be in compose file
2services:
3 app:
4 environment:
5 - LOG_LEVEL=info
6 - MAX_CONNECTIONS=100
7 - FEATURE_NEW_UI=true
8
9# Secrets: should come from .env or Docker secrets
10 environment:
11 - DATABASE_PASSWORD=${DATABASE_PASSWORD}
12 - API_KEY=${EXTERNAL_API_KEY}
13 - JWT_SECRET=${JWT_SECRET}
14
15# Even better: use Docker secrets for sensitive data
16 secrets:
17 - db_password
18
19secrets:
20 db_password:
21 file: ./secrets/db_password.txt

07Generating Secure Secrets

Never use weak passwords or predictable values. Generate cryptographically secure random strings for all secrets.
1# Generate a random password (32 characters)
2openssl rand -base64 32
3
4# Generate a hex string (useful for keys)
5openssl rand -hex 32
6
7# Generate multiple secrets at once for .env
8cat << EOF > .env
9POSTGRES_PASSWORD=$(openssl rand -base64 32)
10APP_SECRET_KEY=$(openssl rand -hex 32)
11JWT_SECRET=$(openssl rand -base64 48)
12EOF
13
14# On macOS without openssl
15cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1

Store generated secrets in a password manager before adding to .env. You'll need them for recovery.

08Validating Required Variables

Catch missing variables early by using required variable syntax or a validation script.
1# Using required variable syntax - fails if not set
2services:
3 app:
4 environment:
5 - DATABASE_URL=postgres://${POSTGRES_USER:?POSTGRES_USER is required}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@db/${POSTGRES_DB:?POSTGRES_DB is required}
6
7# Or validate in an entrypoint script
8# entrypoint.sh:
9# #!/bin/sh
10# : "${DATABASE_URL:?DATABASE_URL must be set}"
11# : "${SECRET_KEY:?SECRET_KEY must be set}"
12# exec "$@"

09Best Practices Summary

1. Always use .env files, never hardcode secrets in compose files 2. Provide .env.example as documentation 3. Add .env to .gitignore immediately 4. Use strong, randomly generated secrets 5. Distinguish secrets from configuration 6. Consider Docker secrets for highly sensitive data 7. Use different .env files for different environments 8. Validate required variables fail fast