Skip to content
All posts
6 min readcommit f86bc39

$715 to $44 a month: leaving Kubernetes

A managed cluster for a workload that fits on one small instance. What the migration actually involved, and the one component we expected to be hard.


The platform ran on EKS across two environments. It worked. It also cost roughly $715 a month to serve a workload whose steady state was four containers and a Postgres database, and the bill was dominated by things that had nothing to do with running our code.

Where the money went

  • Control plane, at extended-support pricing — $0.60/hr rather than $0.10/hr, six times the rate for a Kubernetes version we had not upgraded
  • NAT gateways, billed hourly and per gigabyte, for outbound traffic that could have gone straight out
  • Load balancers, one per environment, in front of services with modest traffic
  • Node capacity sized for scheduling headroom rather than for load

None of that is Kubernetes being badly priced. It is the cost of an architecture built for a scale we did not have, and would not have for a long time.

The component we expected to be hard

Media was the reason to be cautious. We used STUNner, a media gateway that exists specifically to get WebRTC into a Kubernetes cluster — it is built around the Gateway API and has no meaning outside one. Leaving Kubernetes meant replacing it.

It turned out to be the easiest part, because STUNner had been solving a problem we were about to stop having. It exists to bridge a public client to a media server with no routable address. On a single VM the media server has a public address, so the entire class of problem disappears. Plain coturn, one config file, no application changes.

The hardest component to replace was the one whose reason for existing we were removing.

What we run now

One t4g.small with Docker Compose, an Elastic IP, Caddy terminating TLS, coturn for relay, and RDS on the smallest instance that fits. The marketing site is static on S3 and CloudFront, deliberately independent of everything else so it survives whatever the application is doing.

The Kubernetes manifests are still in the repository. Not out of sentiment: the migration is only reversible while they exist, and the shape of this workload is genuinely a good fit for a cluster once concurrency justifies more than one machine. What we removed was a cluster running ahead of the demand for it.

What we would tell someone considering it

The savings are real and the operational surface shrinks a lot — but so does the safety net. There is no rescheduling onto another node, no rolling update, and one availability zone. For a product at this stage those are acceptable trades that must be made deliberately, with an AMI snapshot ready so a rebuild is minutes rather than an afternoon.

And be honest about which parts of your stack exist to solve problems the new architecture does not have. In our case that was the single most expensive component to reason about, and it deleted itself.

This is the platform that broke

Voxera runs WebRTC transport, streaming speech-to-text, your model and text-to-speech as one pipeline — including the parts described above, now that they work.