A well-built Docker image is like a program: build it once, then feed it different inputs. Those inputs - database URLs, API keys, feature flags - arrive as environment variables. This lesson shows every way to pass configuration into containers and the conventions that keep it safe and maintainable.
Baking configuration into an image means rebuilding for every environment and, worse, risks shipping secrets inside image layers that anyone who pulls the image can read. The twelve-factor app methodology says it plainly: store config in the environment. One image, many environments.
The -e flag sets variables when a container starts:
docker run -d \
-e POSTGRES_USER=app \
-e POSTGRES_PASSWORD=s3cret \
-e POSTGRES_DB=shop \
postgres:16Official images document the variables they understand - the Postgres image, for example, creates a user and database from exactly these settings on first start. You can also forward a variable already set in your shell without repeating its value:
export API_KEY=abc123
docker run -e API_KEY my-api # value taken from the host environmentTyping a dozen -e flags gets old fast. Put them in a file instead:
# app.env
DATABASE_URL=postgres://app:s3cret@db:5432/shop
REDIS_URL=redis://cache:6379
LOG_LEVEL=info
docker run -d --env-file app.env my-apiKeep one env file per environment (dev.env, staging.env, prod.env) and add all of them to .gitignore and .dockerignore so credentials never enter version control or image layers.
Use the ENV instruction for sensible, non-secret defaults that runtime flags can override:
FROM node:20-alpine
ENV NODE_ENV=production
ENV PORT=3000docker run -e PORT=8080 my-api # overrides the Dockerfile defaultA useful rule: ENV for defaults, -e or env files for environment-specific values, and never put secrets in a Dockerfile.
Inside the container, variables appear in the normal process environment:
// Node.js
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;# Python
import os
db_url = os.environ.get("DATABASE_URL")Verify what a running container actually sees with:
docker exec my-api envDocker has two variable instructions that are easy to mix up:
docker build --build-arg VERSION=2.0 .). It is not available in running containers.Never pass secrets through ARG either - build arguments can be recovered from image history.
Environment variables are convenient but visible to docker inspect and to any process in the container. For production-grade secret handling, prefer your orchestrator or cloud secret manager - Docker Swarm and Kubernetes both mount secrets as in-memory files, and Compose supports a secrets: section. At minimum: keep secrets out of images, out of git, and out of shell history.
-e for one-offs and --env-file for full environment definitions.docker exec <name> env.Next up: Docker Compose for Multi-Container Apps, where all of this configuration moves into one declarative YAML file.