How I Cut a Healthtech's Four-Figure AWS Bill in a Week (and Why That Was the Easy Part)
In my first week at a new job, I cut a four-figure-a-month AWS bill enough to make the CTO who had just hired me happy. It looked like the perfect start.
What neither of us imagined was that the underlying problem was only just beginning, and that eight months later it would force us to rebuild the most critical piece of the system. This is the story of that fight — and of the cost mistake you're very probably making right now.
The problem: I was hired to put out a fire
In early 2025 I joined a digital health startup, and they hired me for one very specific thing: the AWS bill. It was thousands of dollars a month, way out of proportion to the actual size of the product, and on top of that the system kept going down intermittently. Everything — and I mean everything — lived inside a single EC2 instance.
The brief was clear: bring the cost down without taking the product down, and without leaving AWS. In a health company, those outages aren't a minor detail either: every minute without service is someone who can't reach something that might genuinely matter to them. So it wasn't only about money — it was about reliability. I had to cut the bill and stabilize the system at the same time, with no room to break anything in the process.
Week 1: the win that was a trap
When you see a bloated bill, you go for the obvious. And the obvious was right there:
- The EC2 instance was massively oversized.
- There were orphaned components — volumes attached to nothing, reserved IPs — billing by the hour.
- The managed resources weren't tuned to the real load.
I did right-sizing: look at how much CPU and memory were actually used, compare it to what was provisioned, and close that gap. In a single week, the bill dropped noticeably. The CTO, who had brought me in for exactly this, was thrilled.
The catch: that quick win was the trap. I'd only patched the symptom. The monolith on a single EC2 still had no elasticity: you pay for the peak even when nobody's around at 3 a.m. I bought breathing room, not the war.
The migration: App Runner + Amplify
With some room to breathe, I went deeper: I moved the backend to AWS App Runner (managed autoscaling) and the frontend to Amplify. The difference is huge: instead of paying for a machine that's on 24/7, you pay for what you use. Multiply that across the dead hours of a product with ~150 users and you get the savings.
And this time it was for real: costs stayed low and stable for six to eight months. It looked like the end of the road.
I called the case closed. I'd gone from a fragile, expensive monolith to an elastic architecture that cost a fraction. On paper, I'd done my job — and for months, the numbers backed me up.
The interlude: "let's add Kubernetes"
During those calm months, the CTO proposed implementing Kubernetes. His reason wasn't technical: it's what the big companies use. And the pressure to say yes was real: it came from the person who had just hired me, at a moment when everything was going well and it was easy to get swept along.
I stopped on a single number: we had ~150 users. Kubernetes for 150 users isn't scaling; it's strapping on a 40-pound backpack to cross the street. And it isn't free even if the software is: someone stands up the cluster, maintains the nodes, patches, and watches that nothing falls over at midnight.
So I said no — with the user count on the table. Don't get me wrong: Kubernetes is excellent when you have dozens of services and a platform team. The problem isn't the tool; it's using it for a scale you don't have. (Hold on to this "no": eight months later it proved its worth in gold.)
The cost that came back
Those calm months went by and the costs started climbing again. And I want to be clear here, because it's where many people assume wrong: it didn't slip past us. We were watching the bill closely, and the moment the line moved we saw exactly where it came from. Compute was flat; what was growing was the database. We were watching it in real time — it was eating us alive and it showed on every chart.
The general principle, the one I want you to take away, is this:
Cost doesn't disappear: it moves layers. You optimize the part you can see and the bottleneck jumps to the next one.
It's not that the database was "hidden": it's that, until Aidbox grew big enough, it simply wasn't the bottleneck. Once it was, it jumped to the front of the line. The challenge was never diagnosis — we had that crystal clear — it was that solving it would be far harder than any quick tweak suggested.
The real culprit: the data layer
The culprit had a name: Aidbox, the FHIR platform we used as the backend for clinical data. It had grown, and now it was hammering PostgreSQL hard enough that the system started to slow down.
If you don't know FHIR, it's the standard healthcare systems use to store and exchange clinical data; its queries can be surprisingly heavy, and the more data you pile up, the more expensive every corner of the database gets. This wasn't an optional add-on: it was the core of the product asking for resources.
My first instinct was the direct one: if the system was slow because of the database, I gave it more database. I scaled it up. The slowness went away… until next month's bill arrived: it had spiked. I'd just bought performance with money, the easiest and most expensive trade of all.
Then we optimized queries hard, everything that could be squeezed. And even so, the system didn't scale. That problem, on top of others we were already dragging along with Aidbox, led us to an uncomfortable conclusion: we'd hit the limit of the tool. It was no longer about optimizing it further; we needed to build our own.
It was a race against the clock — and against the AWS bill — to migrate off Aidbox onto an in-house backend. (The how and the why of that solution is a whole post of its own; I'll save it for another day.) The ending: we went from spending thousands of dollars a month just on the database to barely a few hundred — a reduction of more than 90%.
And here's the closer that ties it all together: the final problem was about data, not compute or orchestration — so much so that we ended up replacing the data layer itself. That "no" to Kubernetes didn't just save complexity: Kubernetes would have fixed absolutely nothing here. Time proved me right.
The cost was never a single thing to fix; it was a pattern I chased across every layer — compute first, data later — until the answer stopped being optimize and became replace. Seeing it as a pattern, and not as isolated incidents, is what changed how I audit a bill.
What we learned
- Don't over-provision. The same mistake showed up twice: the EC2 at the start and the database at the end. Over-provisioning out of fear costs as much as out of hype.
- Cost moves layers. A quick win can blind you to the underlying problem.
- Learn to say no — even to the person who just hired you. The best technical deliverable is sometimes a well-argued "no."
- Question the constraints, but respect them when you have to. I proposed another provider on cost; I was told to stay on AWS and I worked within that rule.
- Know when to optimize and when to replace. We squeezed Aidbox down to the last query and it still didn't scale; building our own is what actually brought the bill down, from thousands to hundreds a month.
If you're going to audit your own bill
Three questions I start with today:
- What resource grows while everything else stays flat? The slope points to the culprit better than the total.
- Am I paying for peaks that rarely happen? If so, your problem is elasticity, not size.
- Does my fix solve today's problem or the one I imagine three years out? If it's the second, you're probably over-provisioning.
Is your cloud bill climbing and you suspect the culprit is hiding in a layer you're not looking at? At Evoheim we audit exactly this kind of architecture. Get in touch and we'll dig in together.