Software Engineering

Configuration Is Where Systems Quietly Get Complicated

Nobody plans a configuration problem. It accumulates one environment variable at a time until deployments depend on knowledge that lives in someone's head instead of in the repository.

Why configuration sprawl becomes one of the least visible sources of production risk, and how teams end up with environments that only work because of undocumented settings.

Jay McBride

Jay McBride

Software Engineer

4 min read

Introduction

No team decides to build a configuration problem.

It arrives one variable at a time. A feature needs a toggle. An integration needs a key. A customer needs slightly different behavior, so a threshold moves into an environment variable instead of the codebase.

Each decision is reasonable. Two years later, staging and production differ in nineteen ways, three of which nobody can explain, and a deploy fails because a variable that was never written down is missing.

This article is for teams whose environments have started drifting apart in ways that only surface during releases. The drift is not the failure. It is the warning that came before it.

The Core Judgment: Configuration Is Code That Escaped Review

Application code gets reviewed, tested, versioned, and traced back to a decision.

Configuration usually gets none of that. It lives in a deployment dashboard, a secrets manager, a CI setting, and someone’s local .env file — four places with four different edit histories and no shared review.

Yet configuration decides real behavior. It picks which database you talk to, whether retries are enabled, how long a session lasts, which features exist. Those are consequential choices being made outside the process built to catch consequential choices.

The problem is not that configuration exists. It is that it grows in the one part of the system with the weakest guardrails.

How This Breaks in the Real World

The first symptom is environments that stop resembling each other.

Staging behaves differently, so the team stops trusting it — a slow, expensive loss covered elsewhere. Then a deploy fails at boot because production needed a variable nobody knew production needed.

The second symptom is worse: nobody can answer what a setting is for.

You find a variable named something like LEGACY_SYNC_MODE, set to 2, in production only. Whoever set it left. The commit that introduced it says “fix sync.” Changing it feels dangerous, so it stays forever, and the system carries a decision no living person understands.

The third symptom is the one that causes outages. A value that was safe at one scale — a connection pool size, a timeout, a batch limit — becomes wrong as traffic grows. Nothing alerts on it, because it was never treated as a thing that could be wrong.

A Real Example: The Setting That Only Worked by Accident

I inherited a service where a background worker processed a queue in fixed-size batches, and the batch size came from an environment variable.

Production had it set roughly four times higher than staging. Nobody remembered doing it. The service ran fine for over a year.

Then an upstream change made each item in the batch modestly more expensive to process. Staging was fine. Production started hitting memory limits and restarting workers mid-batch, which produced duplicate side effects for a subset of items.

The bug was not the batch size. The bug was that a load-bearing production value had never been reviewed, documented, tested, or matched anywhere else — and the only environment that could have caught the problem was configured not to.

What I Would Do Instead

I treat configuration as part of the system’s design surface rather than as deployment trivia.

In practice:

  • keep the full list of settings in the repository, even when the values live elsewhere
  • fail loudly at startup on anything required and missing, rather than defaulting quietly
  • write down what a setting is for and what a wrong value does, next to its definition
  • keep non-secret values as close to identical across environments as the environments allow
  • treat a deliberate production-only difference as something to document, not something to remember

That last point is where most of the value is. A production-only setting is sometimes correct and sometimes an accident, and after six months nobody can tell which one they are looking at.

I also delete aggressively. A toggle that has been on for a year is not a toggle. It is a branch of dead code with an on-switch, and removing it is cheaper than carrying the ambiguity.

Closing

Configuration is the part of the system that changes most often and gets examined least.

It escapes code review, rarely appears in tests, and accumulates decisions faster than anyone documents them. Then it produces a failure that looks mysterious and turns out to be a value somebody set for a good reason in 2023.

The complexity was never really in the code.

It was in the settings nobody could explain.

Share

Pass it to someone who needs it

About the Author
Jay McBride

Jay McBride

Software engineer with 20 years building production systems and mentoring developers. I write about the tradeoffs nobody mentions, the decisions that break at scale, and what actually matters when you ship. If you've already seen the AI summaries, you're in the right place.

Based on 20 years building production systems and mentoring developers.

Support my work on Buy Me a Coffee
Keep Reading

More Articles

/ 3 min read

Runbooks Are Boring Until the Incident Belongs to You

Teams postpone runbooks because documentation feels secondary during calm periods. Then an incident lands on the wrong person at the wrong time and institutional memory turns out to be a very weak system.

Read article
/ 3 min read

Cross-Functional Ownership Sounds Great Until Nobody Owns Production

Shared responsibility can improve collaboration. It can also become the sentence teams use when accountability is too blurry to survive an incident cleanly.

Read article