Environment variables are the standard way to configure containers: database URLs, API keys, feature flags, ports. Docker offers several ways to set them, and Compose adds a second, easily confused mechanism. This guide untangles them.
With docker run
docker run -d -e NODE_ENV=production -e PORT=3000 myapp
docker run -d -e API_KEY myapp # passes API_KEY's value from your shell
docker run -d --env-file ./app.env myapp
app.env format — one KEY=value per line, no export, no quotes needed:
NODE_ENV=production
DATABASE_URL=postgres://app:secret@db:5432/app
# comments are allowed
Quotes in an env file are taken literally by docker run (the value includes the quote characters). Compose handles quotes more like a shell does.
Check what a container received:
docker exec myapp env
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' myapp
In Docker Compose: two different things
1. environment and env_file — variables inside the container
services:
app:
image: myapp
environment:
NODE_ENV: production
PORT: "3000"
env_file:
- app.env
2. .env and ${VAR} — interpolation in the Compose file itself
Compose reads a file named .env next to compose.yaml and substitutes ${VAR} anywhere in the YAML:
# .env
APP_TAG=2.4.1
DB_PASSWORD=change-me
services:
app:
image: myapp:${APP_TAG}
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
The key point: .env does not automatically put variables into containers. It only feeds ${…} substitutions. A variable reaches a container only if it’s listed under environment: or in an env_file:.
See the resolved result:
docker compose config
Defaults and required values
image: myapp:${APP_TAG:-latest} # default if unset or empty
password: ${DB_PASSWORD:?DB_PASSWORD is required} # fail with a message
Precedence
When the same variable is set several ways in Compose, the order (highest first) is roughly:
docker compose run -eon the command lineenvironment:in the Compose file (including interpolated values)env_file:ENVin the image’s Dockerfile
For interpolation, your shell environment overrides .env. So APP_TAG=2.5.0 docker compose up -d uses 2.5.0 regardless of the file.
ARG vs ENV in a Dockerfile
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-slim
ARG BUILD_SHA
ENV NODE_ENV=production
LABEL git.sha=$BUILD_SHA
- ARG exists only during the build (
docker build --build-arg BUILD_SHA=abc123). It’s not available to the running app — but its value can still be visible in the image history, so never pass secrets as build args. - ENV is baked into the image and present at runtime. Use it for sensible defaults that can be overridden with
-e.
Secrets: don’t leak them
Environment variables are convenient but visible to anyone who can run docker inspect, and they can end up in logs or crash reports. For sensitive values:
- Keep
.envand env files out of Git (.gitignore) and readable only by you:chmod 600 .env. - Prefer files for secrets. Many official images support
_FILEvariants:
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Compose mounts the file at /run/secrets/db_password inside the container; it never appears in docker inspect output.
- Never bake secrets into images with
ENVorARG.
See Docker secrets and secrets management.
Changing variables
Containers read their environment at creation. After editing .env or environment::
docker compose up -d # recreates services whose config changed
docker compose restart does not pick up changes — it restarts the same container with the old environment.
Troubleshooting
- Variable is empty in the container — it was only in
.env(interpolation) and not listed underenvironment:/env_file:. WARN The "X" variable is not set. Defaulting to a blank string.— Compose can’t find it in the shell or.env; check the file name and location, or use--env-file.$in a password breaks things — Compose treats$as interpolation; escape it as$$in the Compose file or.env.- Value has stray quotes — quotes in a
--env-filefordocker runare literal; remove them.
Related: Docker Compose tutorial and Dockerfile tutorial.