Back to Blog
DevOps12 min readJanuary 5, 2024

Environment Variables in Docker: A Complete Guide

Docker offers multiple ways to manage environment variables. Understanding each method and when to use it is crucial for secure, maintainable containerized applications.

Methods for Setting Environment Variables

Docker provides several ways to pass environment variables to containers, each with different use cases and security implications.

1. Command Line with -e Flag

The simplest method is passing variables directly when running a container:

# Single variable
docker run -e DATABASE_URL=postgres://localhost/db myapp
# Multiple variables
docker run -e DATABASE_URL=... -e API_KEY=... myapp
# Pass from host environment
docker run -e DATABASE_URL myapp

Pros: Simple, no files needed
Cons: Variables visible in process lists, hard to manage many variables

2. Environment Files (--env-file)

Load variables from a file instead of the command line:

# .env file
DATABASE_URL=postgres://localhost/db
API_KEY=secret123
DEBUG=true
# Run with env file
docker run --env-file .env myapp
# Multiple env files
docker run --env-file .env --env-file .env.local myapp

Pros: Easy to manage, can use different files for different environments
Cons: File must exist on host, still plaintext

3. Dockerfile ENV Instruction

Set default environment variables in the image itself:

# Dockerfile
FROM node:20-alpine
# Set defaults (can be overridden at runtime)
ENV NODE_ENV=production
ENV PORT=3000
# Use build args for build-time values
ARG VERSION
ENV APP_VERSION=$VERSION

Important:Never put secrets in ENV instructions! They're baked into the image and visible to anyone with image access.

4. Docker Compose

Docker Compose offers the most flexible environment configuration:

# docker-compose.yml
version: '3.8'
services:
app:
image: myapp
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://db:5432/app
env_file:
- .env
- .env.local

Variable Interpolation

Compose automatically loads a .env file in the same directory and allows variable substitution:

# .env (in same directory as docker-compose.yml)
DB_PASSWORD=supersecret
APP_VERSION=1.2.3
# docker-compose.yml
services:
app:
image: myapp:${APP_VERSION}
environment:
- DB_PASSWORD=${DB_PASSWORD}
- CACHE_SIZE=${CACHE_SIZE:-100} # Default value

5. Docker Secrets (Swarm/Kubernetes)

For production environments, Docker Secrets provide encrypted storage:

# Create a secret
echo "supersecretpassword" | docker secret create db_password -
# Or from a file
docker secret create db_password ./db_password.txt
# docker-compose.yml (Swarm mode)
services:
app:
secrets:
- db_password
secrets:
db_password:
external: true

Secrets are mounted as files at /run/secrets/secret_name. Your application reads the file contents rather than an environment variable:

// Node.js example
const fs = require('fs');
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

Multi-Stage Builds and Secrets

Be careful with secrets during build time. Use Docker BuildKit's secret mounting:

# Dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
# Mount secret only during this RUN command
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm install
# Secret is NOT in the final image
FROM node:20-alpine
COPY --from=builder /app /app
# Build with secret
DOCKER_BUILDKIT=1 docker build --secret id=npm_token,src=.npmrc .

Production Best Practices

1. Never Bake Secrets into Images

Environment variables set with ENV or ARG in Dockerfiles are visible in the image layers. Always inject secrets at runtime:

Bad - secret in image:
ENV API_KEY=supersecret123
Good - secret injected at runtime:
docker run -e API_KEY=$API_KEY myapp

2. Use Read-Only Secrets

When using secret files, mount them read-only to prevent accidental modification:

docker run -v ./secrets:/secrets:ro myapp

3. Different Configs for Different Environments

Use compose file overrides for environment-specific configuration:

# docker-compose.yml - base config
# docker-compose.dev.yml - dev overrides
# docker-compose.prod.yml - prod overrides
# Development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up

4. Validate Environment at Startup

Have your application validate required environment variables on startup:

// entrypoint.js
const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
console.error(`Missing env vars: ${missing.join(', ')}`);
process.exit(1);
}

5. Use .dockerignore

Prevent .env files from being copied into images:

# .dockerignore
.env
.env.*
*.local
.git
node_modules

Key Takeaways

  • Use --env-file for simple deployments
  • Use Docker Compose for complex multi-container setups
  • Use Docker Secrets for production-grade secret management
  • Never bake secrets into Docker images
  • Use BuildKit secret mounts for build-time secrets
  • Validate required environment variables at container startup