Environment variables and configuration

Docker · Practical · 10 min

One image should run in dev, staging and production; configuration is what changes between them.

Build images once and configure them per environment at run time, following the twelve-factor idea that config lives in the environment, not in the image.

Ways to pass configuration:

  • -e LOG_LEVEL=debug: one variable. -e API_KEY with no value passes through the variable from your shell.
  • --env-file prod.env: many KEY=value lines from a file.
  • Config files via a bind mount or volume, for structured configuration like nginx.conf.
  • Secrets via files: orchestrators (Compose, Swarm, Kubernetes) mount secrets as files, and many official images accept *_FILE variants such as POSTGRES_PASSWORD_FILE.

In the Dockerfile there are two variable instructions, often confused:

  • `ARG` exists only during the build. Set it with --build-arg. Useful for versions: ARG NODE_VERSION=22 then FROM node:${NODE_VERSION}. An ARG declared before FROM must be redeclared inside the stage to be used there.
  • `ENV` is set during the build and persists into every container as a default, overridable with -e.

Neither is a safe place for secrets. Both are recorded in the image (docker history and docker inspect reveal them), and environment variables of a running container are visible to anyone with access to docker inspect. Use BuildKit secret mounts at build time and secret files at run time.

Make your app read its config from the environment with sane defaults, and fail fast with a clear message when a required variable is missing.

Example

# Dockerfile excerpt
#   ARG APP_VERSION=dev          # build time only
#   ENV PORT=8000 LOG_LEVEL=info # defaults for every container
#   LABEL version=$APP_VERSION

docker build --build-arg APP_VERSION=1.4.2 -t api:1.4.2 .

cat > prod.env <<'END'
LOG_LEVEL=warn
DATABASE_URL=postgres://app@db:5432/app
END
docker run -d --env-file prod.env -e PORT=9000 -p 9000:9000 api:1.4.2
docker exec $(docker ps -lq) printenv LOG_LEVEL PORT    # warn / 9000

docker inspect --format '{{json .Config.Env}}' api:1.4.2   # ENV is visible!

Remember

  • Build once, configure at run time with -e, --env-file and mounted files
  • ARG = build time only; ENV = build time and container default
  • ARG and ENV values are visible in image metadata: never put secrets there

Watch out for

  • Baking a production API key into the image with ENV — anyone who can pull the image can read it.

Check yourself

  1. Which Dockerfile instruction's value is NOT available to the running container?
  2. Why shouldn't secrets be passed as build args?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/reference/dockerfile/#env