Kubernetes bills get away from small teams for a boring reason: you pay for capacity you requested, not for CPU you used. A cluster that is "only 15% busy" can still cost a production SaaS month because every Deployment asked for more than it needs, and three replicas of everything run through the weekend.
This is the order we use on cloud and DevOps work after the app is already on a cluster. If you are still choosing whether to be on Kubernetes at all, start with Docker vs Kubernetes for startups. Rightsizing a cluster you should not have is the expensive path.
Measure the Unit That Invoices You

Cloud invoices for nodes, disks, load balancers, NAT, and idle load - not for "Kubernetes." Inside the cluster, the lever you actually own is:
- Requests - the scheduler's reservation. This is what forces extra nodes.
- Limits - the ceiling. Too low and you OOM; too high and you learn nothing.
- Replica count - including HPAs that never scale to zero
- Storage class and leftover PVCs
- Control plane and add-ons you forgot (ingress, metrics, a second ingress "for staging")
Pull 14 days of container_cpu_usage and working-set memory per workload. Compare to requests. Anything requesting 4× usage is a candidate. Do this before buying a FinOps SaaS.
A boring AWS baseline still applies: tags, one account per environment if you can, an alarm on the cluster's monthly forecast.
Requests and Limits First
Most "Kubernetes is expensive" incidents are over-requests.
Practical default for a typical Node/API pod:
- Request CPU near p70 usage, not the peak of a one-off deploy
- Request memory near steady working set + 20-30%, then watch OOMKills for a week
- Set a limit so a runaway loop cannot eat the node - but do not set CPU limits so tight that throttling becomes your latency bug
Do not copy resources: from a tutorial that assumed a 16-core laptop. Commit the numbers in Git with the Deployment so the next engineer does not "fix" them back up.
Vertical Pod Autoscaler sounds appealing. On a small cluster it is another controller to debug. Manual requests after two weeks of metrics is usually enough.
Idle Replicas and the Weekend Cluster
Three replicas of an internal admin app that three people use is a habit, not a requirement.
| Workload | Default question |
|---|---|
| Public API | What is p95 at night? Can HPA go to 1? |
| Workers | Scale on queue depth, not on a fixed 3 |
| Cron | Job/CronJob, not a Deployment that sleeps |
| Staging | One replica; sleep the namespace overnight if the team can bear it |
| Dev clusters | Should this be a namespace on staging, or a Compose file? |
Cluster autoscaler only helps if pods fit. Tiny over-requests fragment nodes so you add a machine for 200m CPU. That is the opposite of optimization.
If your "HA" is three replicas of a service with a single-AZ database, you bought a story. Put HA where the data is.
Add-Ons, Ingress, and Disk
Quiet bill items:
- Multiple ingress controllers and unused load balancers
- default StorageClass on fast SSD for logs and scratch
- metrics-server + Prometheus + a second APM agent scraping everything at 15s
- NAT gateway hairpin from private nodes that only need ECR pulls (use VPC endpoints)
Image size is not a line item until pull time blocks scale-out. Multi-stage Dockerfiles still matter; they just do not show up as "Kubernetes."
When Managed Containers Are Cheaper
If you have one or two services, ECS Fargate, Cloud Run, or Container Apps often beat a small EKS/GKE/AKS cluster once you count:
- Control plane (or the "free" tier that stopped being free)
- Two nodes because you wanted HA
- An engineer's hours on upgrades
Kubernetes wins when many workloads share nodes and you already have someone who can upgrade the cluster. It does not win as a résumé item.
Terraform should describe whichever target you pick - see Terraform on AWS for beginners - so a later move is a module change, not folklore.
A Two-Week Pass
Days 1-3: Export usage vs requests; kill unused LoadBalancers and PVCs. Days 4-8: Patch requests on the top five spenders; watch error rates and p95. Days 9-14: Drop obvious idle replicas; add an HPA where traffic is spiky; set a budget alarm.
If the bill is still dominated by two always-on fat nodes for a single API, you are on the wrong product. Move the API. Keep Kubernetes for the mix that actually shares the grid.
FAQ
Should we autoscale everything to zero? Scale to zero is for bursty, latency-tolerant workers and true scale-to-zero platforms. A user-facing API that cold-starts for 20 seconds is not a saving. It is a support ticket.
Is spot cheaper? For batch and stateless workers, yes if you can tolerate interruption. For a single replica of checkout, no.
Will a service mesh save money? No. It adds sidecars. Adopt it for a traffic policy you can name, not for a dashboard.
The Bottom Line
Kubernetes cost optimization is requests that match reality, replicas that match traffic, and an honest check that you still need a cluster. Idle YAML is a standing order to the cloud.
If the cheaper design is "one service on a managed container product," take it. Fashionable orchestration is not a deliverable.
Need a cluster rightsized - or a path off Kubernetes you should not have started? See cloud and DevOps or get a free estimate.
