Skip to content
Knowledge Hub

12-factor config: one artifact, many environments, fail fast Intermediate

This cluster answers: where config lives, and how to load and check it so that one image runs correctly in every environment, and missing config kills the process at startup instead of at request time.

The payment service deploys at 17:00. The pod starts, the health check returns 200, traffic flows in. At 17:20 every refund request fails with 401: PAYMENT_API_SECRET_KEY was misnamed in the Secret, the code reads process.env.PAYMENT_API_SECRET_KEY ?? '' and sends an empty key.

A config error does not kill the process. It leaves the process alive and wrong. The goal of this cluster: make every config error a startup error, before the first request, with a message that lists every bad key.

Loading diagram…

Part The question it answers
12-factor and build once What is config according to 12-factor? Why not build a separate image for production?
Parse and validate env How do you write a typed config module? Which keys may have defaults? How to name keys and handle a local .env?
Kubernetes and runtime Are ConfigMap/Secret safe? Does a config change need a restart? Rotation, feature flags, drift?
Common mistakes Which lines of env-reading code cause bugs, and how to fix them? Plus a production checklist.

Decision table: type of setting → where it lives

Section titled “Decision table: type of setting → where it lives”
Type of value Example Where it lives Why
Business constant, identical in every environment MAX_ITEMS_PER_ORDER = 50, order code regex Code (constant) Changing it changes behavior; it needs review and tests like code
Varies by environment, not secret DATABASE_POOL_MAX, PAYMENT_API_BASE_URL, LOG_LEVEL Env var (from a ConfigMap) Exactly the 12-factor definition of config
Secret DB password, API key, signing key Secret manager → injected as env var or file Access control, audit, rotation; see Secure coding
Toggled at runtime, per user or by % new checkout for 5% of users, kill switch Feature flag service Changes without a deploy, with targeting and audit
Business data edited by operators shipping fee per province, list of banks Database (with an admin screen) This is data, not config; it needs history and edit permissions

Rule of thumb:

  • Same in every environment → code.
  • Changes per deploy → config.
  • Changes per user or by time of day → flag or data.

Apply to every service that runs in more than one environment: a typed config module, validation at startup, no defaults for secrets and production URLs.

Reasonable exceptions:

  • One-off scripts, internal CLIs: reading process.env directly with minimal checks is enough.
  • Large structured config (routing rules, tenant lists): a file mounted from a ConfigMap or the database, still validated on load. Env vars do not suit deeply nested structures.
  • Highly sensitive secrets: read from a mounted file or call the secret manager directly instead of env vars. OWASP notes that env vars can be read by other processes or leak into logs and dumps.

Self-check:

  1. Why does rebuilding a separate image for production violate “build once, deploy many”, even if the code is identical?
  2. What is the harm during a deploy if loadConfig reports errors one key at a time?
  3. You edit a ConfigMap but the pod still runs the old value: what are two possible causes?
  4. What must the app support so that webhook secret rotation has no window where signatures are rejected?

Next: 12-factor and build once.