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
-eor--env-fileto 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
.envfile 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
ENVin 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 rundoes 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
ENVin the Dockerfile. - Committing
.envto git. - Assuming
env_file:values override explicitenvironment:entries (they don't). - Expecting
ARGbuild args to be available at runtime. - Relying on Docker Compose secrets without enabling the required backend (Swarm).
Best practices
- Use
env_fileorenvironmentfor 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
.envto.gitignoreand provide an.env.examplewith 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
ARGfor build-time values andENVfor runtime defaults; never bake secrets into images. - Use
env_fileorenvironmentin Compose for container config, and.envfor substitution – but don't commit secrets. - For production secrets, prefer a secrets manager or your PaaS secret mechanism rather than environment files.