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 Compose23# Database Configuration4POSTGRES_USER=myapp5POSTGRES_PASSWORD=super_secret_password_1236POSTGRES_DB=myapp_production78# Application Settings9APP_PORT=808010APP_DEBUG=false11APP_SECRET_KEY=another_random_secret1213# External Services14SMTP_HOST=smtp.example.com15SMTP_PORT=58716SMTP_USER=notifications@example.com03Using 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 set4 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}1011 db: 12 image: postgres:1513 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 configuration2# Copy this file to .env and fill in the values34# Database Configuration (required)5POSTGRES_USER=myapp6POSTGRES_PASSWORD=change_me_in_production7POSTGRES_DB=myapp89# Application Settings10APP_PORT=808011APP_DEBUG=true12APP_SECRET_KEY=generate_a_random_string_here1314# External Services (optional)15# SMTP_HOST=smtp.example.com16# SMTP_PORT=587Add .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 structure2project/3├── docker-compose.yml4├── .env # Default (usually development)5├── .env.production # Production values6├── .env.staging # Staging values7└── .env.example # Template89# Run with specific environment10docker compose --env-file .env.production up -d1112# Or set in shell before running13export $(cat .env.production | xargs) && docker compose up -d06Secrets 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 file2services: 3 app: 4 environment: 5 - LOG_LEVEL=info6 - MAX_CONNECTIONS=1007 - FEATURE_NEW_UI=true89# Secrets: should come from .env or Docker secrets10 environment: 11 - DATABASE_PASSWORD=${DATABASE_PASSWORD}12 - API_KEY=${EXTERNAL_API_KEY}13 - JWT_SECRET=${JWT_SECRET}1415# Even better: use Docker secrets for sensitive data16 secrets: 17 - db_password1819secrets: 20 db_password: 21 file: ./secrets/db_password.txt07Generating 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 3234# Generate a hex string (useful for keys)5openssl rand -hex 3267# Generate multiple secrets at once for .env8cat << EOF > .env9POSTGRES_PASSWORD=$(openssl rand -base64 32)10APP_SECRET_KEY=$(openssl rand -hex 32)11JWT_SECRET=$(openssl rand -base64 48)12EOF1314# On macOS without openssl15cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1Store 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 set2services: 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}67# Or validate in an entrypoint script8# entrypoint.sh:9# #!/bin/sh10# : "${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