When You Say 'Overengineered,' What Do You Mean?
A company needed to know when their dashboard was down. Not eventually. Not after a customer called. Now.
The dashboard was internal, custom-built, sitting behind a login page. The kind of tool that twenty people use every day and nobody thinks about until it stops working. When it stopped working, which it did occasionally, nobody could tell the support team what was actually broken. “The dashboard is down” is not a diagnosis. It’s a symptom. And the difference between a blank screen, a login failure, and a half-loaded page with stale data is the difference between rebooting a service, rotating a credential, and calling the developer who built it three years ago.
So they asked for monitoring. Not the kind that pings a URL and checks for a 200. That tells you the server is alive. It doesn’t tell you the page works. It doesn’t tell you the login flow completes. It doesn’t tell you what the user sees when something goes wrong. They needed to see the failure. A screenshot of the broken state, sent somewhere the team would actually look.
The tool was straightforward. A Playwright script that opens a headless browser, navigates to the login page, authenticates, checks the dashboard, and if anything looks wrong, takes a screenshot and sends it to a Telegram channel. The team already lived in Telegram. That’s where they’d see it.
The script ran in a Docker container on a cron. Every fifteen minutes, the headless browser would wake up, check the page, and go back to sleep. If the page was fine, silence. If it wasn’t, a screenshot landed in the channel with enough context to know what broke.
The question is what happened next.
Two paths, same container
The fast way to deploy this: SSH into the server, pull the Docker image, run it. One command. Maybe a docker-compose.yml if you’re feeling generous. Set the environment variables in the compose file, docker compose up -d, close the terminal. Five minutes, and you have monitoring.
That’s what most people would do. That’s what most people have done, on servers all over the world, for containers exactly like this one. It works. The container runs. The screenshots arrive. The problem is solved.
Here’s what actually happened instead.
The container definition went into a docker-compose.yaml checked into a Git repository on the company’s GitHub. The Dockerfile that built the image was in another repo, also on company GitHub, with a CI pipeline that pushed the built image to a private container registry. The deployment itself was managed through Terraform, using a Portainer provider that declared the stack as code. The Terraform state was stored in a shared S3 bucket with locking enabled. The variables (API keys, tokens, endpoints) were defined in a variables file with types, descriptions, and a .tfvars.example that showed the next person what they’d need to fill in.
For a container that checks whether a webpage is working.
Read that list again. Git repository. CI pipeline. Container registry. Terraform. Portainer provider. S3 state backend. State locking. Variable definitions. Example configuration.
For a health check.
If you felt something while reading that list… a twitch, a tightening, a word forming in the back of your head… what was the word?
Was it “overengineered”?
What does the word contain?
Let’s stay with that feeling for a moment. Don’t dismiss it. It’s real. Something about that list feels like too much. Too many layers for too small a problem. A health check container doesn’t need Terraform. It doesn’t need a state backend. It doesn’t need variable definitions with types and descriptions.
Does it?
What happens when the developer who wrote the Playwright script leaves the company? With the “simple” deploy, the container is running on a server. Somewhere. The environment variables are in a compose file. On the server. The image was pulled from… where? Was it built locally on someone’s laptop? Is the Dockerfile on that laptop too? Is the laptop still in the building?
The Telegram token in the environment variables. Who created it? Which bot? Does anyone else have it? If the bot gets revoked, who knows how to create a new one and where to put it?
The container is running. The monitoring works. Everything is fine. Until someone needs to change it. Until someone needs to understand it. Until someone needs to reproduce it on a different server because this one is being decommissioned.
Now look at the “overengineered” version. The Dockerfile is on GitHub. Anyone in the company can read it, fork it, modify it. The compose file is next to the Terraform configuration, in a repository with a history of every change. The Terraform state shows exactly what’s deployed, where, and with what configuration. The .tfvars.example tells the next developer exactly what they need. The S3 backend means the state isn’t on someone’s laptop. The locking means two people can’t accidentally overwrite each other’s changes.
How long does it take to understand the “simple” deployment? It depends on whether the person who set it up is still answering their phone.
How long does it take to understand the “overengineered” one? About as long as it takes to read a file.
You deployed it in five minutes. How long will it take the person after you to understand what you deployed?
Who pays for simplicity?
There’s a pattern hidden in the word “overengineered” that I can’t stop noticing.
When someone says a solution is overengineered, they’re almost always evaluating it from the perspective of day one. The day it ships. The day the container starts running and the screenshots start arriving. On day one, the Terraform setup takes longer. The CI pipeline takes longer. The variable definitions take longer. On day one, the “simple” version wins every comparison.
But who lives on day one?
The person who deploys it lives on day one. Everyone else, the person who maintains it, the person who debugs it at midnight, the person who onboards six months later and needs to understand what’s running and why, they live on day 90. Day 180. Day 365. And on those days, which version is simpler?
The “simple” deployment is simple for the deployer. The “overengineered” deployment is simple for everyone who comes after. When you call something overengineered, are you measuring the cost for you, or the cost for them?
And this is where the word “overengineered” starts to crack. Because it contains an assumption so deeply buried that most people never examine it: that the person deploying today and the person maintaining tomorrow are the same person. That your context will always be available. That your understanding of the system will persist as long as the system does.
How often is that true?
The container doesn’t know
The container doesn’t care how it was deployed. It runs the Playwright script. It checks the dashboard. It sends the screenshot. It has no opinion about whether its compose file lives in a Git repository or on a server’s filesystem. It doesn’t know whether its image was pushed through a CI pipeline or built on someone’s laptop at 11 PM.
But the next person will care. The person who gets the 3 AM page because the container stopped running and nobody knows why. The person who joins the team and asks “what monitoring do we have?” The person who needs to move the infrastructure to a new server because the old one is end-of-life.
For them, the difference between “it’s all in a repo, with state on S3, and here’s the variable template” and “I think it’s running on that server, ask around” is not a difference in engineering philosophy. It’s a difference in hours. Days. Sometimes weeks.
The Terraform configuration for this health check container is about forty lines. The compose file is eight lines. The variable definitions are thirty lines. That’s the “overengineering.” Less than a hundred lines of code that say, to anyone who reads them: here is what exists, here is why, here is how to change it.
Less than a hundred lines. That’s the distance between “overengineered” and “I have no idea what’s running on that server.”
When you say “overengineered,” do you mean “more than I would have done”… or “more than the problem requires”? Because those aren’t the same measurement. One measures your effort. The other measures the system’s needs. And the system will outlive your involvement with it.
Pick any running service in your infrastructure. Can you show a new hire, in under five minutes, what it does, why it’s there, and how to change it?
If not… who is that simplicity serving?