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:

  1. docker compose run -e on the command line
  2. environment: in the Compose file (including interpolated values)
  3. env_file:
  4. ENV in 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 .env and env files out of Git (.gitignore) and readable only by you: chmod 600 .env.
  • Prefer files for secrets. Many official images support _FILE variants:
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 ENV or ARG.

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 under environment:/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-file for docker run are literal; remove them.

Related: Docker Compose tutorial and Dockerfile tutorial.

  • Docker
  • Docker Compose
  • Configuration