On Overengineering (DevOps Edition)

On Overengineering (DevOps Edition)
I think this cat was about to scratch me in Murcia, Spain. Autumn 2025 © Sergio Fernández

Overengineering is usually framed as a “software developer problem”: too many layers, too many abstractions, too many features nobody asked for.

DevOps and infrastructure folks do the same thing. We just do it with VPCs, VPNs, Terraform modules, Kubernetes clusters, and security policies instead of classes and interfaces.

And when we do it, the blast radius is often bigger: networks, access, security, and deployment pipelines are shared surfaces. An overengineered infra decision can slow down every engineer in the company for years.

This is a post about that: how overengineering shows up in DevOps, why it happens, and what it did to me personally on one particularly “brilliant” project.


What I Mean by Overengineering

I don’t mean:

  • “Using technology X where Y would have been better.”
  • “This architecture is a bit complex.”
  • “Resume-driven engineering with the shiny new thing.”

Those can be symptoms, but for this post I’ll use a narrower, more painful definition:

Overengineering is building infrastructure and tooling that nobody needs right now.
(Or: building for imaginary future problems instead of real current ones.)

“Future proofing,” “platform thinking,” “we’ll need this later” — all nice words that often hide the same thing: we’re sinking time and money into complexity that isn’t justified by today’s requirements.

In DevOps, that usually looks like:

  • Designing for 20 tenants when we have 2.
  • Building a multi-region, multi-cloud landing zone for a single-region app.
  • Creating generic network overlays for partners we don’t have.
  • Writing hyper-generic Terraform modules that cover 5 clouds but nobody can understand.

Let me tell you about one of those.


The Network Project That Could Have Been "an Email"

Years ago, I worked on a project to interconnect multiple companies’ networks. Think:

  • Several independent organizations.
  • Different IP ranges, different environments.
  • Need secure connectivity between them.

Simple on paper: agree which routes each side uses, avoid conflicts, set up connectivity, test, done.

The senior engineer responsible for the design had a different vision. They asked a question that sounded very smart in a meeting:

“What if the routes collide in the future?”

Not “do they collide today?” — they didn’t.
Not “can we just agree on who uses what?” — we absolutely could have.

Instead of exchanging a document with “here are our used prefixes, here are yours, let’s coordinate,” the chosen solution was:

  • Build a dynamic, network-level system to detect and handle potential route collisions between all parties.
  • Make it clever, automated, and “future proof”.
  • Spend months engineering for the possibility of conflicting routes that… never really happened in practice.

I still remember the phrase from the call with the Delivery Manager:

“It could be useful in the future.”

And it was… exactly once. In years.

Meanwhile:

  • It cost the client months of engineering time.
  • It made the network design significantly more complex.
  • It became a piece of infrastructure that only a couple of people truly understood.

Did it look great on a CV and in a slide deck? Absolutely.
Was it the best solution for the company? I honestly don’t think so.


The Hidden Costs: Maintenance, Security, and Misery

The initial build was only the visible part. The real cost of overengineering showed up later.

1. Maintenance Hell

That fancy system didn’t just exist; it had to keep existing:

  • When something broke, guess who had to dive back into the design and re-learn all of it?
    Me, multiple times, over multiple years.
  • Every time we changed something adjacent, we had to tiptoe around the complexity so we wouldn’t break obscure edge cases.

And of course:

  • The senior engineer who designed the whole thing left after the project shipped.
  • The bus factor on this design was effectively 1, and that bus drove away.

Overengineering is a great way to manufacture long-term maintenance work for people who never got a say in the original decision.

2. Security Surface Explosion

As part of the design, we were issuing wildcard domains.
Great for flexibility, also great for attackers.

Wildcard domains (and their corresponding certs) are:

  • Perfect for phishing/spoofing:
    anything.customer.example.com is now possible, and looks legit.
  • larger attack surface: any misconfigured or compromised subdomain can be abused.
  • Harder to reason about: “What exactly can exist under this wildcard?” is rarely documented well.

We didn’t need that flexibility for the actual business requirements.
We just made the system more “elegant” and “generic” — and, as a side effect, more fragile and more exploitable.

3. Onboarding Pain

Every new engineer touching that part of the infra had to:

  • Learn this bespoke network behavior.
  • Decode half-finished design docs plus tribal knowledge.
  • Spend hours understanding a problem domain that could have been “we share a route sheet on day one and update it when something changes”.

There is a special kind of frustration in wasting half a day on a system you know, deep down, should never have existed in this form.

4. Documentation as a Separate Project (Red Flag)

We even had a separate person assigned to document the project.

That sounds great on paper. In practice:

  • Documentation lagged far behind reality.
  • It was never really “done.”
  • It only reached a decent state years later, and only because enough incidents and onboarding sessions forced us to improve it.

Needing a dedicated doc project for a single subsystem is already a smell.
Sometimes it’s necessary (regulatory, extremely complex domains), but often it’s just a sign the design is too clever.

5. Meanwhile, the Basics Were on Fire

While all this elegant networking magic existed, our basics were… not great:

  • Terraform structure was a mess.
  • Large parts of the infrastructure weren’t really managed as proper IaC.
  • There was plenty of copy-paste, manual steps, and snowflake environments.

But because those issues weren’t “client-facing” or “innovative,” they never got attention.

This is another common DevOps overengineering pattern:

Overinvest in flashy, visible complexity.
Underinvest in boring, invisible hygiene.

Guess which one actually makes your life better in the long run.


Why Seniors Fall Into This Trap

I don’t think the senior engineer on that project was malicious or incompetent. The design was genuinely clever. That was part of the problem.

A few patterns show up over and over when seniors overengineer infra:

1. Solving Boredom, Not Business Problems

Sometimes the root cause is simple: too much slack and not enough clear constraints.

When you have:

  • Extra time,
  • A mandate to “own” a domain,
  • And no strong pushback from product or management,

it’s very tempting to:

  • Invent interesting problems,
  • Design elegant solutions,
  • And build a platform for hypothetical future scenarios.

It’s engineering as a hobby project, funded by the company.

2. Optimizing for CVs and Conference Talks

Complex infra looks impressive:

  • “We built a dynamic multi-tenant route collision system across X networks.”
  • “We have a fully generic cross-org connectivity platform.”

It sounds way cooler than:

  • “We made a spreadsheet and a simple peering setup.”
  • “We agreed on IP ranges like adults.”

The incentives are misaligned. Careers reward complex achievements. And businesses mostly need boring reliability.

3. Fear of Future Pain (and Misplaced YAGNI)

There’s also a rational, but misplaced, fear:

  • “If we don’t think ahead, we’ll have to re-architect everything later.”
  • “We’re already here in this code/infra; let’s build the general solution now and avoid future pain.”

That fear is understandable. But two things are usually true:

  1. You are terrible at predicting the future shape of constraints.
  2. You’re creating new, guaranteed pain today to avoid hypothetical pain tomorrow.

In my story, we optimized hard for “what if routes collide in some future we can’t describe,” and paid for it immediately and repeatedly.


Overengineering in DevOps: How It Usually Shows Up

Outside that one project, I’ve seen (and sometimes contributed to) these DevOps-flavored overengineering patterns:

  • Kubernetes for no reason
    Tiny app, low traffic, simple failure modes — but we ship a full-blown k8s cluster with service mesh, ingress controllers, and custom operators. (I love K8s, but... too many times the same tale)
  • Terraform modules from hell
    One “universal” module that supports every cloud, every environment, 50 flags, and 100 lines of count/for_each gymnastics. Nobody dares touch it.
  • Enterprise-grade networking for a startup
    Full multi-account setup, complex shared services VPCs, transit gateways with route tables per spoke, when a couple of VPCs and simple peering would have done the job for years.

The pattern is the same: We build a platform for an imagined scale and complexity that we don’t currently have.


What Seniors Should Be Focusing On

If you’re in a senior DevOps / SRE / infra role, your value is not measured in how clever your solutions look. It’s measured in:

  • How reliable the systems are.
  • How quickly the team can change them safely.
  • How understandable they are to other engineers.
  • How cheap they are to run and maintain over time.

Concretely, that usually means focusing on very unsexy things:

  • Solid, boring IaC hygiene:
    • Clear repo structure.
    • Simple, composable modules.
    • No magic.
  • Good observability:
    • Logs, metrics, traces that make incidents short and boring.
  • Tight access and security:
    • Minimal attack surface, not maximal “flexibility.”
  • Straightforward network topologies:
    • Fewer layers, fewer special cases, more documentation.
  • Onboarding clarity:
    • A new engineer can understand what’s going on without a two-week guided tour.

All of those reduce the need and the temptation to overengineer.


How I Try to Avoid Doing This Again

I still have the instinct to design for future elegance. Now I try to fight it with constraints and questions like these:

  1. Who is asking for this, in concrete terms?
    • Name the user, team, or customer.
    • If it’s “future teams” or “maybe partner X”, that’s a red flag.
  2. What breaks if we do the dumb, simple version first?
    • Example: “We just exchange route documents and set up static rules.”
    • If the answer is “nothing, except my pride,” do the simple thing.
  3. What’s the cheapest reversible step?
    • Can we start with the naïve design and only add generality if/when we feel real pressure?
  4. Who will maintain this in 2 years?
    • If the answer is “probably not me,” that’s more reason to keep it boring.
  5. Is this complexity protecting revenue or just my sense of cleverness?
  6. If this thing never gets used at scale, will we regret building it?
    • If the answer is yes, that’s a strong smell.

A good DevOps design often feels slightly underwhelming. That’s fine.
Underwhelming, well-documented, and cheap-to-maintain beats impressive and fragile.


Closing Thoughts

That network project “worked.” It shipped. It solved a problem that might have existed. It also:

  • Burned a lot of client money.
  • Increased our security surface.
  • Slowed onboarding for years.
  • Required repeated re-learning by people who never chose the design.
  • Became legacy the moment the author left.

Overengineering in DevOps doesn’t always look like an explosion of microservices or some wild new framework. Sometimes it’s a single, very clever system doing a job that could have been done by a spreadsheet, a shared doc, and a couple of simple static configurations.

If you’re in a position to design infrastructure:

  • Bias for boring.
  • Solve the problem in front of you.
  • Make it easy for the next engineer.
  • Add complexity only when real, painful constraints force your hand.

Your future self — and whoever inherits your systems after you leave — will thank you. Even if your CV looks a bit less glamorous.

Sergio Fernández

Sergio Fernández

Senior Cloud DevOps Engineer specializing in Kubernetes.
Murcia, Spain