Configuration is the most under-designed part of your system
It has no type system, no tests, no review, and it causes a disproportionate share of outages.
Look at the last ten significant outages you can remember reading about. A disproportionate number were caused by configuration, not code.
A config file with a wrong value. A feature flag flipped. A generated file that doubled in size. A DNS record. A permission change.
Configuration receives a fraction of the engineering rigor that code does, and it has comparable power to break things.
why it goes wrong#
No type system. A YAML file will happily contain timeout: 30s where the code expects an integer, or enabled: "false" which is a truthy string.
No tests. Nobody writes a test for their config.
No review, or perfunctory review. A config change is "just a value" and gets approved in ten seconds.
Deployed differently from code. Frequently faster, frequently without staging, frequently without a canary. The safety mechanisms built for code deployment routinely do not cover config.
Environment drift. Staging and production differ in ways nobody has enumerated, so staging validates a configuration that is not production's.
No rollback story. "What was this value before" is often unanswerable.
the fixes#
1. Parse and validate at startup, not at use.
The worst failure mode is a config error that manifests three hours later when a rarely-used code path reads a malformed value.
Validate everything at boot. Fail loudly and immediately if anything is wrong.
class Settings(BaseModel):
database_url: PostgresDsn
timeout_ms: int = Field(gt=0, le=60_000)
max_connections: int = Field(ge=1, le=1000)
feature_new_checkout: bool = False
settings = Settings(**load_config()) # raises at startup, with a clear messageAn application that will not start is far better than one that starts and behaves wrongly.
2. Types, with a schema.
Whatever your language, there is a library that turns untyped config into a validated typed object. Use it. This eliminates the entire category of string-that-should-be-a-number bugs.
3. Config changes go through the same pipeline as code.
Version control. Review. Staging. Canary. Rollback.
The argument against is that config changes need to be fast, especially for incident response. That is a real need and the answer is a small explicitly-defined set of emergency levers — kill switches, rate limits — with a fast path, and everything else on the normal pipeline.
Not "all config is fast" because that is how config takes down your service.
4. Validate generated configuration.
If a config file is produced by a program, that program can be wrong. A size check against the previous version, a schema check, a sanity check on record count.
This is the specific failure that has caused several high-profile outages: a generated file changed unexpectedly and propagated globally before anyone looked at it.
5. Make the environment explicit and diff it.
You should be able to answer "how does staging differ from production" with a command. If you cannot, staging is not validating production.
config-diff staging production6. Log the effective configuration at startup.
Not the file — the resolved values after defaults, overrides, and environment variables are applied. Redact secrets. This is the single most useful thing for debugging "it works on my machine," because the effective config is frequently not what anyone thinks it is.
7. Feature flags need the same discipline as code.
Owner. Expiry. Test both branches. Log the flag state on every event. A flag flip is a production change and should be treated as one.
the hierarchy that works#
Most systems end up with layered configuration and the layering should be explicit and simple:
defaults in code
← config file
← environment variables
← command line flagsLater overrides earlier. Log which layer each effective value came from when debugging.
Keep the layers few. Systems with six overlapping sources of configuration — defaults, file, environment, a service, a database table, a flag system — produce values nobody can trace. Each layer you add makes "why is this value what it is" harder to answer.
secrets#
Not the same thing as configuration and should not be in the same place.
- Never in the config file. Never in version control. Never in the image.
- A secret manager, injected at runtime.
- Rotated on a schedule, and the rotation must be tested.
- Never logged. Redact by default at the logger, not at each call site — because somebody will forget.
the general point#
Configuration is the input to your program that is most likely to be wrong and least likely to be checked.
Every rigor you apply to code — types, validation, tests, review, staged rollout, rollback — applies to configuration, and applying it costs a day of setup.
The reason it does not happen is that config does not feel like code. It is data, and data feels safe.
It is not data. It is the arguments to your program, and passing wrong arguments to a program is how programs go wrong.
— Dom, June 24, 2026