Environment Variables
Environment variables are the standard way to configure containers. They let you change application behavior -- database URLs, API keys, feature flags -- without rebuilding the image. This lesson covers every way to set them and the security practices you need to follow.
Why Environment Variables?
One image, many environments. Configuration lives outside the image, injected at runtime.
Setting Variables with -e
The -e flag sets a single environment variable:
docker run -d \
--name api \
-e NODE_ENV=production \
-e DB_HOST=db.internal \
-e DB_PORT=5432 \
my-api:1.0.0
Passing Host Variables
If you omit the value, Docker passes the variable from your host shell:
export API_KEY=abc123
docker run -d -e API_KEY my-api:1.0.0
# Container receives API_KEY=abc123
Using Env Files (--env-file)
When you have many variables, use an env file to keep commands clean:
docker run -d --env-file .env my-api:1.0.0
Env File Format
# Database configuration
DB_HOST=db.internal
DB_PORT=5432
DB_NAME=myapp
DB_USER=appuser
DB_PASSWORD=s3cret
# Application settings
NODE_ENV=production
LOG_LEVEL=info
PORT=8080
Rules:
- One
KEY=VALUEper line - Lines starting with
#are comments - No quotes needed around values (quotes are included literally)
- No spaces around
= - Empty lines are ignored
In env files, quotes are not stripped. DB_HOST="localhost" sets the value to "localhost" (with quotes). Write DB_HOST=localhost instead.
Multiple Env Files
You can use multiple --env-file flags. Later files override earlier ones:
docker run -d \
--env-file .env.defaults \
--env-file .env.production \
my-api:1.0.0
Environment Variables in Docker Compose
Compose offers two directives: environment (inline) and env_file (from file).
Inline with environment
services:
api:
image: my-api:1.0.0
environment:
NODE_ENV: production
DB_HOST: db
DB_PORT: "5432"
LOG_LEVEL: info
From File with env_file
services:
api:
image: my-api:1.0.0
env_file:
- .env.defaults
- .env.production
Combining Both
services:
api:
image: my-api:1.0.0
env_file:
- .env.defaults
environment:
# Overrides take precedence over env_file
NODE_ENV: production
LOG_LEVEL: debug
Precedence order (highest wins):
environmentdirective (inline)- Shell environment variables
env_filedirectiveENVin Dockerfile (build-time default)
Variable Substitution in Compose
Compose can interpolate host environment variables using ${VAR} syntax:
services:
api:
image: my-api:${APP_VERSION:-latest}
environment:
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD must be set}
| Syntax | Behavior |
|---|---|
${VAR} | Use value of VAR, empty string if unset |
${VAR:-default} | Use default if VAR is unset or empty |
${VAR-default} | Use default only if VAR is unset |
${VAR:?error} | Fail with error message if VAR is unset or empty |
Viewing Container Variables
# List all environment variables in a running container
docker exec my-container env
# Check a specific variable
docker exec my-container printenv DB_HOST
# Using inspect (shows variables set at creation)
docker inspect -f '{{json .Config.Env}}' my-container | python3 -m json.tool
Build-Time vs Runtime Variables
| Type | Set With | Available At | Persists in Image? |
|---|---|---|---|
ARG | --build-arg | Build time only | ❌ No |
ENV | -e, --env-file | Build + Runtime | ✅ Yes (as default) |
# ARG: only available during build
ARG APP_VERSION=1.0.0
# ENV: baked into image as default, overridable at runtime
ENV NODE_ENV=production
ENV APP_VERSION=${APP_VERSION}
# Override ENV at runtime
docker run -e NODE_ENV=development my-api:1.0.0
Security Best Practices
Do
| Practice | Why |
|---|---|
Use --env-file for secrets | Keeps secrets out of shell history and docker inspect |
Add .env to .gitignore | Prevents secrets from being committed |
| Use Docker secrets (Swarm) | Secrets mounted as files, not visible in env |
| Rotate secrets regularly | Limits blast radius of a leak |
Don't
| Anti-Pattern | Risk |
|---|---|
docker run -e PASSWORD=secret | Visible in docker inspect and shell history |
Hardcode secrets in Dockerfile ENV | Baked into every image layer permanently |
Commit .env files to git | Secrets exposed in repository history forever |
| Log environment variables at startup | Secrets appear in log aggregation systems |
Never put secrets in ENV instructions in your Dockerfile. They are baked into the image layers and visible to anyone who can pull the image. Use runtime injection with --env-file or Docker secrets instead.
Common Patterns
Database Connection
docker run -d \
--name api \
-e DB_HOST=db.internal \
-e DB_PORT=5432 \
-e DB_NAME=myapp \
-e DB_USER=appuser \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
my-api:1.0.0
Feature Flags
docker run -d \
-e ENABLE_CACHE=true \
-e ENABLE_METRICS=true \
-e LOG_LEVEL=warn \
my-api:1.0.0
Multi-Environment Setup
LOG_LEVEL=info
PORT=8080
CACHE_TTL=300
DB_HOST=prod-db.internal
DB_PASSWORD=<injected-by-CI>
LOG_LEVEL=warn
Key Takeaways
- Environment variables are the standard way to configure containers without rebuilding images.
- Use
--env-fileinstead of-efor secrets to keep them out of shell history. - In Compose,
environment(inline) overridesenv_filevalues. - Never bake secrets into Dockerfile
ENVinstructions -- they persist in image layers. - Add
.envfiles to.gitignoreand rotate secrets regularly.
What's Next
- Continue to Container Resource Management to learn how to monitor and control resource usage.