Configuration and Environment Management
The same application build usually runs in several places — a developer's laptop, CI, preview environments, staging, and production — and should behave differently in each only because its configuration differs: which database it talks to, which payment account it uses, how verbose its logs are, how long it waits before timing out. Getting configuration right means one tested artifact can be promoted from environment to environment with confidence. Getting it wrong produces some of the most common and embarrassing outages: a typo in a timeout, a staging API key in production, a missing variable that only fails on the first real payment.
Configuration management answers a handful of questions deliberately: what counts as configuration, where each value lives, how the application reads and validates it, who can change it, and how a change is rolled out and rolled back.
TL;DR
- Build once, configure per environment: configuration is everything that varies between deployments; everything else stays in code.
- Read configuration once at startup into a typed, validated object, and fail fast with a clear message if anything is missing or invalid.
- Keep secrets separate from ordinary settings, injected from a secrets manager and never committed.
- Layer sources with a documented precedence: defaults, environment files, environment variables, command-line flags.
- Prefer explicit settings over checks of the environment's name.
- Treat configuration changes like code changes: versioned, reviewed, validated, rolled out gradually, and easy to revert.
Quick Example
A single configuration module in TypeScript that parses environment variables with Zod, applies defaults, converts types, and stops the process on invalid input:
The rest of the codebase imports config and never touches process.env directly, so every setting has one definition, one type, and one place to document it.
Core Concepts
What Counts as Configuration
The Twelve-Factor App defines configuration as everything likely to vary between deploys:
- Resource locations: database, cache, queue, and service URLs.
- Credentials: API keys, passwords, certificates — the subset called secrets.
- Per-deployment values: public hostnames, regions, tenant or account identifiers.
- Tuning: timeouts, pool sizes, concurrency limits, log levels.
Internal wiring that is the same everywhere — routes, dependency injection, business rules — is code, not configuration. Making it configurable adds options nobody tests.
Where Configuration Lives
Layering and Precedence
Most applications combine sources: defaults in code, an environment-specific file, environment variables, and occasionally flags. Document the precedence — typically later sources override earlier ones — and log the effective configuration at startup with secrets redacted, so "what value is this instance actually using?" has a quick answer.
Static Versus Dynamic Configuration
Static configuration is read once at startup; changing it means restarting or redeploying, which keeps behavior predictable and ties every change to a release. Dynamic configuration changes while the process runs, which is faster but needs validation, gradual rollout, and a way to revert. Feature flags are dynamic configuration with targeting rules; use them for releasing features and operational kill switches, not for values such as database URLs.
Secrets Are Different
Secrets need stricter access control, audit logs, rotation, and protection from accidental exposure. Store them in a secrets manager, inject them at deploy time or fetch them at startup with short-lived credentials, and never commit them to repositories or bake them into images. See Secrets Management.
Environments
Parity
Differences between environments should be limited to configuration values, scale, and data. Same database engine and version, same container images, same configuration mechanism. Infrastructure as code makes this achievable; changes made by hand in one environment are how "it worked in staging" happens.
Ask for Behavior, Not the Environment Name
Explicit settings let a preview environment use real email sandboxes, or production run a temporary diagnostic mode, without new code paths.
Kubernetes Example
Ordinary settings in a ConfigMap and credentials referenced from a Secret:
Kubernetes Secrets are only base64-encoded by default; enable encryption at rest and restrict access, or sync them from an external secrets manager.
Changing Configuration Safely
Google's SRE book estimates that roughly 70% of outages come from changes to live systems, and configuration changes are changes. Treat them with the same discipline as code:
- Version configuration in Git, so every value has a history and an author.
- Review changes, especially to production.
- Validate in CI: schema checks, policy rules (no debug logging in production, timeouts within sane bounds), and a dry run against the application's own configuration loader.
- Roll out gradually: to one instance, region, or percentage of traffic first, with monitoring.
- Roll back quickly: keep the previous version one command away.
- Detect drift: compare what is running with what is in the repository, and alert on differences.
Best Practices
Fail Fast at Startup
A missing database URL should stop the process with a clear message during deployment — where the rollout halts — not cause the first user request to fail an hour later. See Backend Service Lifecycle and Graceful Shutdown.
Default the Harmless, Require the Important
Provide defaults for tuning values such as log level and timeouts. Require explicit values for anything environment-specific or dangerous — database URLs, credentials, external endpoints — so production never silently runs with a development default.
Put Units in Names
REQUEST_TIMEOUT_MS, MAX_UPLOAD_BYTES, and CACHE_TTL_SECONDS prevent the classic mistake of a value meant as seconds being read as milliseconds.
Document Every Setting
Generate documentation, or a .env.example, from the schema: name, type, default, whether it is secret, and what it does. Delete settings nobody uses; every option is something that can be misconfigured.
Common Mistakes
Secrets Committed to the Repository
String Flags That Are Always True
Environment Variables Read Everywhere
Hand-Edited Production Settings
FAQ
Should I use environment variables or configuration files?
Often both. Environment variables suit values that differ per deployment and are supported by every platform; files suit structured defaults and settings that benefit from comments. Whatever the sources, load them through one module with a documented precedence.
Is it safe to keep secrets in environment variables?
It is common and acceptable with care, but environment variables can leak through crash reports, debug endpoints, child processes, and logs that dump the environment. For sensitive credentials, prefer secrets mounted as files or fetched from a secrets manager, and make sure error reporting scrubs them.
What is the difference between configuration and feature flags?
Configuration describes the environment the application runs in and is usually fixed per deployment. Feature flags control product behavior at runtime, often per user or tenant, to release features gradually or switch them off quickly. Flags need their own lifecycle, including removal once a rollout is complete.
How do we keep staging like production?
Build both from the same infrastructure code and container images, use the same database engine and versions, and limit differences to configuration values, scale, and data. Review differences regularly; each one is a place where staging can pass while production fails.
Should configuration be hot-reloadable?
Only where fast changes are worth the complexity — operational switches, rate limits, flags. Hot reload needs validation before applying, atomic switching to the new values, and visibility of which version is active. For most settings, a quick redeploy is simpler and safer.
Related Topics
- Backend Development — Building server applications
- The Twelve-Factor App — Configuration in the environment and other principles
- Secrets Management — Storing, injecting, and rotating credentials
- Feature Flags — Runtime switches for releases and operations
- Infrastructure as Code — Keeping environments consistent
- Test Data and Environment Management — Environments for testing