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_KEYwith no value passes through the variable from your shell.--env-file prod.env: manyKEY=valuelines 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
*_FILEvariants such asPOSTGRES_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=22thenFROM node:${NODE_VERSION}. AnARGdeclared beforeFROMmust 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.