Environment Variables & Secrets
Environment variables are the standard way to pass configuration into Docker containers without hard-coding values in your image. This page shows simple, copy‑pasteable examples for Docker and Docker Compose and explains common pitfalls and safer alternatives for secrets.
Concept explained
- ENV in a Dockerfile sets build/runtime defaults baked into the image.
- ARG are build-time only and are not available at runtime.
- docker run uses
-e
or--env-file
to set container environment variables. - Docker Compose can set env vars with
environment:
orenv_file:
and supports variable substitution with${VAR}
. - Secrets should not be committed or baked into images; prefer a secrets manager or your host/PaaS secret features for production.
Docker Compose reads a .env file (for substitution) in the Compose directory; env_file:
injects variables into containers. Behavior and precedence differ – see the "Variations & gotchas" section.
Step-by-step example
- Create a minimal Node app (example reads env vars)
app/index.js
const port = process.env.PORT || 3000;
const db = process.env.DATABASE_URL || "sqlite://:memory:";
console.log("PORT", port);
console.log("DATABASE_URL", db);
require("http")
.createServer((req, res) => res.end("ok"))
.listen(port);
- Dockerfile (use ARG for build-time and ENV for runtime defaults)
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ARG BUILD_TIME_VAR
ENV NODE_ENV=production
ENV PORT=3000
CMD ["node", "index.js"]
- .env (keep out of git)
# .env - local development only
PORT=4000
DATABASE_URL=postgres://user:pass@db:5432/mydb
SECRET_KEY=dev-secret
Never use weak passwords like "dev-secret" in production. Use a secure secret generator.
- docker-compose.yml (variable substitution + env_file)
services:
web:
build: .
ports:
- "${PORT:-4000}:3000"
env_file:
- .env
environment:
- NODE_ENV=${NODE_ENV:-development}
- DATABASE_URL=${DATABASE_URL}
db:
image: postgres:14
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=mydb
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
- Run locally
- Populate
.env
(do not commit it). - Start:
docker compose up --build
- Equivalent docker run (without compose)
- Using flags:
docker run -p 4000:3000 -e DATABASE_URL=postgres://user:pass@host:5432/mydb my-image
- Using an env file:
docker run --env-file .env -p 4000:3000 my-image
Variations & gotchas
- Variable substitution vs. container env:
- Compose uses a
.env
file for substitution into the Compose file itself.env_file:
injects variables into the container environment.
- Compose uses a
- Precedence (common rule):
- Explicit
environment:
entries override values fromenv_file:
. Shell environment variables used when doing substitution override.env
.
- Explicit
- Dockerfile ENV is baked into the image:
- Don't put secrets in
ENV
in the Dockerfile – those values remain in image layers.
- Don't put secrets in
- Docker secrets (Compose "secrets:" feature) require Docker Swarm mode in many setups – not the same as
env_file
. For local dev, secrets in files are a convenience only. docker run
does not automatically read.env
; use--env-file
.
Never commit .env
or files containing secret keys to version control. Add them to .gitignore
and use platform secrets for production.
Common mistakes
- Baking secrets into the image with
ENV
in the Dockerfile. - Committing
.env
to git. - Assuming
env_file:
values override explicitenvironment:
entries (they don't). - Expecting
ARG
build args to be available at runtime. - Relying on Docker Compose secrets without enabling the required backend (Swarm).
Best practices
- Use
env_file
orenvironment
for non-sensitive config in dev. - Use platform-provided secrets or a secrets manager (Vault, cloud secrets) for production secrets.
- Keep defaults in Dockerfile for non-secret values (ports, NODE_ENV default).
- Validate required env vars at container start and fail fast with a clear error.
- Add
.env
to.gitignore
and provide an.env.example
with non-sensitive keys. - Rotate and limit scope of credentials; use per-project secrets where possible.
For local development, an .env.example
and a script that copies .env.example
→ .env
helps onboard teammates without sharing secrets.
When to use / when not to use
When to use environment variables:
- Configuration that varies between environments (port, DB URL, feature flags).
- Non-sensitive defaults and runtime toggles.
When not to use them:
- Large binary configs, certificates, or long-lived secrets that should live in a secret store.
- Values you must hide from image history – do not bake secrets into images.
Key takeaways
- Use
ARG
for build-time values andENV
for runtime defaults; never bake secrets into images. - Use
env_file
orenvironment
in Compose for container config, and.env
for substitution – but don't commit secrets. - For production secrets, prefer a secrets manager or your PaaS secret mechanism rather than environment files.