Injecting Config: Env and Volumes¶
What You'll Learn¶
- How to combine ConfigMaps, Secrets, plain env vars, and projected volumes for one realistic application
- The exact precedence order when multiple sources define the same environment variable
- Restart-to-pick-up-changes versus live-reload, and how to choose deliberately instead of by accident
Why This Matters¶
The 12-factor app principle is simple: config varies between deploys, code doesn't, so config belongs in the environment, not in the image. Kubernetes gives you four different mechanisms to satisfy that (env, envFrom, ConfigMap volumes, Secret volumes) and none of them enforce discipline on their own — a real application's config is usually a deliberate mix, and getting the mix wrong is a common source of "works on my cluster" surprises.
Mental Model¶
Treat config sources like layers: non-sensitive, rarely-changing values as env vars for simplicity; sensitive values as Secret volumes for the tmpfs and no-crash-dump-leak properties; anything the app should hot-reload without a restart as a plain ConfigMap volume; and per-replica identity (pod name, namespace, IP) via the downward API, merged in with a projected volume when convenient.
How It Works¶
A realistic combined example¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
spec:
replicas: 3
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
annotations:
checksum/config: "a1b2c3d4" # bumped by CI/CD whenever app-config changes
spec:
serviceAccountName: orders-api
containers:
- name: orders-api
image: registry.example.com/orders-api:2.4.0
env:
- name: APP_ENV
value: "production"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: orders-config
key: db_host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: orders-db-credentials
key: password
envFrom:
- configMapRef:
name: orders-feature-flags
volumeMounts:
- name: app-config
mountPath: /etc/orders-api/config
readOnly: true
- name: tls-certs
mountPath: /etc/orders-api/tls
readOnly: true
volumes:
- name: app-config
configMap:
name: orders-config
- name: tls-certs
projected:
sources:
- secret:
name: orders-tls
- serviceAccountToken:
path: token
expirationSeconds: 3600
This one manifest deliberately uses every mechanism for a reason: APP_ENV is a static literal, POD_NAME comes from the downward API for per-replica logging context, DB_HOST is a rarely-changing ConfigMap value read once at startup, DB_PASSWORD is a Secret env var (acceptable here because the app reads it once at boot and never logs environment dumps), feature flags come in bulk via envFrom so new flags don't require manifest edits, and TLS material plus a scoped service account token are merged into one directory via a projected volume.
Environment variable precedence¶
When multiple sources could define the same name, Kubernetes resolves conflicts in a fixed order:
enventries are applied in the order they're listed — later entries with the same name do not override earlier ones; the first definition of a given name inenvwins.envFromentries are applied in list order, each one merging in that source's keys; a naming collision between twoenvFromsources resolves to the last one listed.envalways wins overenvFromregardless of list order — explicitenventries take priority over anything coming from a bulkenvFromimport.
env:
- name: LOG_LEVEL
value: "debug" # this wins over anything from envFrom below
envFrom:
- configMapRef:
name: shared-defaults # even if shared-defaults also defines LOG_LEVEL
- configMapRef:
name: team-overrides # if both configmaps define the same key, this one wins between the two
This ordering is easy to get backwards from memory — always verify with kubectl exec <pod> -- env rather than assuming.
Restart-to-pick-up vs. live-reload¶
| Approach | How it works | Trade-off |
|---|---|---|
| Restart-to-pick-up | Config baked in at container start (env vars); a config change requires a new pod (manual kubectl rollout restart or an automated checksum annotation bump) |
Simple, predictable, guarantees the whole fleet runs one consistent config version at a time |
| Live-reload | App watches a mounted ConfigMap/Secret file (via inotify, polling, or a sidecar like configmap-reload) and reapplies config without restarting |
No downtime for config changes, but the app must implement watching correctly, and a fleet can transiently run mixed config versions during propagation |
Most teams default to restart-to-pick-up for anything correctness-sensitive (database connection strings, feature flags that affect business logic) and reserve live-reload for genuinely hot-swappable things like log levels or a reverse proxy's upstream list.
# Restart-to-pick-up, triggered explicitly
kubectl rollout restart deployment/orders-api
kubectl rollout status deployment/orders-api
Common Mistakes¶
- Assuming
envlist order determines a "last one wins" precedence — it's actually first-one-wins withinenv, which is the opposite of most people's intuition. - Relying on live-reload for a value the application only reads once at process start, then wondering why nothing changes even though the mounted file updated correctly.
- Mixing static, rarely-changing config with per-deploy secrets in a single ConfigMap/Secret, making it impossible to rotate one without touching the other.
- Not bumping a config-checksum annotation (or otherwise forcing a rollout) after a ConfigMap/Secret change, then assuming a "config-only, no-code-change" deploy happened when in fact zero running pods picked it up.
Interview Questions¶
- If both
envandenvFromdefine the same variable name on a container, which one wins, and why is that easy to get backwards? - Design the config strategy for a service that needs a hot-reloadable log level but a restart-required database connection string. What mechanism would you use for each?
- What's the risk of relying purely on live-reload for a fleet of 50 replicas during a config change?
See Interview Prep for full answers.
Next¶
Continue to Helm Fundamentals and Writing Charts to package this kind of manifest for reuse across environments.