Environment Variables & Secrets Management
Following The Twelve-Factor App methodology, an application's codebase should be strictly decoupled from its runtime configuration. In this chapter, explore the mechanisms for injecting environment variables, understand the critical distinction between build-time and runtime parameters, and safeguard sensitive credentials using BuildKit Secret Mounts and Docker Secrets.
1. Runtime Environment Injection via CLI
The Docker Engine allows you to pass environment variables into the container's PID 1 process environment at boot:
# 1. Inline Key-Value Pair (-e)
docker run -d -e PORT=8080 -e NODE_ENV=production my-api:latest
# 2. Inheriting an existing host variable without exposing the value in terminal history:
export STRIPE_API_KEY="sk_live_12345"
docker run -d -e STRIPE_API_KEY my-api:latest
# 3. Loading variables from a centralized .env file (--env-file):
docker run -d --env-file .env.production -p 8080:8080 my-api:latest2. The Fatal Security Trap: ARG vs ENV
Never Pass Secrets via ARG or ENV!
Developers often write ARG GITHUB_TOKEN or ENV DB_PASS=secret inside a Dockerfile.Any variable declared in ARG or ENV is permanently recorded in plain text in the image metadata!
# An attacker or automated scanner can easily extract all build-time ARGs and ENVs: docker history --no-trunc my-image:latest docker inspect my-image:latest
The Solution: BuildKit Secret Mounts
BuildKit allows you to mount secrets temporarily during a specific RUN instruction without committing those bytes to the resulting image layers:
# Dockerfile: Mount secret as an ephemeral in-memory file during npm install
# syntax=docker/dockerfile:1.4
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
# The secret file /run/secrets/npm_token is only visible to this specific RUN layer!
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]Pass the secret file securely during the build command:
docker build --secret id=npm_token,src=./.npmrc.secret -t my-secure-app .3. Compose Variable Interpolation & Validation
Docker Compose parses .env files automatically and supports Bash-style parameter expansions insidecompose.yaml:
| Syntax | Meaning & Behavior | Example |
|---|---|---|
${VARIABLE} | Direct variable substitution. Empty string if unset. | image: "node:${NODE_VERSION}" |
${VARIABLE:-default} | Provides a default fallback value if the variable is unset. | PORT: "${APP_PORT:-3000}" |
${VARIABLE:?error_msg} | Fails build/boot with fatal error if variable is missing. | PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required!}" |
4. Enterprise Docker Secrets (File-Based Convention)
Environment variables can leak through error trace dumps, APM monitoring agents, and docker inspect. The enterprise standard is file-based secret mounting:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app_admin
# Notice the _FILE suffix convention used by official database images:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt/run/secrets/db_password. It never appears in the output of docker inspect or docker stats!Next Up: Docker in CI/CD Pipelines
In Chapter 17, integrate Docker into automated GitHub Actions and GitLab CI workflows with distributed layer caching.
Chapter 17: Docker in CI/CD Pipelines →