Skip to main content

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: or env_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.
note

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

  1. 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);
  1. 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"]
  1. .env (keep out of git)
# .env - local development only
PORT=4000
DATABASE_URL=postgres://user:pass@db:5432/mydb
SECRET_KEY=dev-secret
caution

Never use weak passwords like "dev-secret" in production. Use a secure secret generator.

  1. 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:
  1. Run locally
  • Populate .env (do not commit it).
  • Start: docker compose up --build
  1. 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.
  • Precedence (common rule):
    • Explicit environment: entries override values from env_file:. Shell environment variables used when doing substitution override .env.
  • Dockerfile ENV is baked into the image:
    • Don't put secrets in ENV in the Dockerfile – those values remain in image layers.
  • 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.
caution

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 explicit environment: 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 or environment 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.
tip

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 and ENV for runtime defaults; never bake secrets into images.
  • Use env_file or environment 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.