Environment Variables and Configuration

beginner
9 min

Environment Variables and Configuration

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.

Why Configuration Belongs Outside the Image

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.

Passing Variables at Run Time

The -e flag sets variables when a container starts:

bash
docker run -d \ -e POSTGRES_USER=app \ -e POSTGRES_PASSWORD=s3cret \ -e POSTGRES_DB=shop \ postgres:16

Official 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:

bash
export API_KEY=abc123 docker run -e API_KEY my-api # value taken from the host environment

Env Files

Typing 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
bash
docker run -d --env-file app.env my-api

Keep 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.

Defaults with ENV in the Dockerfile

Use the ENV instruction for sensible, non-secret defaults that runtime flags can override:

dockerfile
FROM node:20-alpine ENV NODE_ENV=production ENV PORT=3000
bash
docker run -e PORT=8080 my-api # overrides the Dockerfile default

A useful rule: ENV for defaults, -e or env files for environment-specific values, and never put secrets in a Dockerfile.

Reading Variables in Your Application

Inside the container, variables appear in the normal process environment:

js
// Node.js const port = process.env.PORT || 3000; const dbUrl = process.env.DATABASE_URL;
python
# Python import os db_url = os.environ.get("DATABASE_URL")

Verify what a running container actually sees with:

bash
docker exec my-api env

ARG vs ENV

Docker has two variable instructions that are easy to mix up:

  • ARG exists only while the image is being built (docker build --build-arg VERSION=2.0 .). It is not available in running containers.
  • ENV is baked into the image metadata and available both at build time and in every container.

Never pass secrets through ARG either - build arguments can be recovered from image history.

A Note on Secrets

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.

Key Takeaways

  • Build one image and configure it per environment with environment variables.
  • Use -e for one-offs and --env-file for full environment definitions.
  • ENV sets overridable defaults; ARG is build-time only; neither is for secrets.
  • Keep env files out of git and out of the build context.
  • Check a container environment any time with docker exec <name> env.

Next up: Docker Compose for Multi-Container Apps, where all of this configuration moves into one declarative YAML file.

Environment Variables and Configuration - Docker | CodeYourCraft | CodeYourCraft