ECS vs EKS: when moving is worth it, and what it actually saves
ECS cannot bin-pack, and that is the whole cost argument. Here is how to tell from two weeks of utilization data whether a migration would pay, what it costs you in operations, and the order to move things in.
The question usually arrives as a cost question. The AWS bill has an ECS line that keeps growing, someone has heard that Kubernetes packs workloads more tightly, and the platform team is asked whether moving to EKS would bring the number down. The honest answer depends less on the platform than on what you are running, so this is the decision the way we walk clients through it, with the numbers from a fleet we measured.
When ECS is the right answer
ECS is a good place to be if most of the following are true.
- You run fewer than about fifteen services and they are not going to multiply.
- Nobody on the team has run Kubernetes in production, and hiring for it is not in the plan.
- Your services are sized roughly right, or you check that periodically.
- You do not need per-pull-request environments, self-service namespaces, or any of the tooling that tends to accumulate on a cluster.
- Fargate's model, where each task is its own billed unit, matches how you think about the workloads.
In that situation a migration buys you an operations burden you did not have, and the packing gains that make EKS cheaper will not show up because there is not enough to pack.
The thing ECS cannot do
ECS cannot bin-pack. Fargate bills each task's reservation in isolation. On the EC2 launch type the scheduler places tasks by reservation, so a host fills up on paper long before it does any work. Neither launch type ever asks whether a task's reservation matches what it uses.
That matters because reservations drift. A service gets deployed at one vCPU and two gigabytes because that was the template, workers are copied from the web task, and nobody shrinks a task once it is running, because on Fargate a smaller task means less headroom and a redeploy with no easy way back.
We measured this on a fleet of 84 services. The production ECS fleet reserved 57 vCPU and used about one. The EC2 container hosts showed 34 percent of CPU reserved and 1 percent used, with memory at 37 percent reserved and 21 percent used. Fargate tasks in public subnets carried 89 public IPv4 addresses, each at a monthly cost, and Fargate's round-robin placement across zones made ECS the largest single source of cross-zone data transfer charges on the bill.
None of that is a sizing mistake somebody made. It is what the platform makes easy.
What changes on EKS
Kubernetes asks the reservation question on every schedule, and a few pieces around it turn the answer into money.
- Requests based on measured usage. Every workload declares CPU and memory requests derived from what it actually consumes. Goldilocks and the vertical pod autoscaler produce recommendations; a human applies them. The request is the unit the scheduler packs on, so this step is what turns over-provisioned tasks into dense nodes.
- Karpenter picks the node. Node pools describe what is allowed: instance families, architectures, spot or on-demand, zones. Karpenter launches the cheapest instance that fits the pending pods and consolidates when a node is empty or underused. Nothing is a fixed-size host that someone sized by hand.
- Spot by default. Spot capacity is 65 to 70 percent cheaper than on-demand. Web and worker pods can move to it through one change in a shared Helm chart, provided the chart also adds the preStop delay and termination grace that let the load balancer drain a pod before it goes.
- Savings plans sized to the on-demand floor. Spot is not covered by compute savings plans, so the plans cover the on-demand baseline and run at full utilization. Adding spot nodes does not add plan waste.
On the cluster we measured, twenty nodes run 522 pods and 84 application workloads. CPU actually used is 23 percent against 66 percent requested, memory 44 percent against 53 percent. Nodes routinely sit at 65 to 89 percent memory, which is what packing looks like. In August the ECS line and the EC2 line on the bill were within a few hundred dollars of each other. One delivered about one vCPU of real work; the other delivered nineteen.
What it costs you
The migration is a platform build, not a lift and shift. Before the first service moves you need GitOps delivery, secrets management, ingress with authentication, observability, a private registry, and a patch cadence for the cluster itself. A cluster you cannot upgrade on schedule is a liability, and on EKS a version is supported for about fourteen months.
The operations burden that Fargate absorbed is now yours. It is smaller than people fear when the cluster is managed from Git, since a production upgrade to EKS 1.33 needed two parameter changes on the platform we run, but it is not zero, and someone has to own it.
And be honest about the bill. On the fleet above, the ECS line fell 47 percent from its January peak to August as workloads moved, but the total monthly spend stayed roughly flat. The EKS build-out added about $1,000 a month net, and that hosts the migrated applications, a rebuilt flagship site, a Postgres data platform, and the whole internal toolchain: Grafana, Loki, Prometheus, Sentry, a registry, CI runners, and an AI sandbox, all on spare capacity. The win is capacity per dollar, not a smaller number at the bottom of the invoice, unless you also stop growing.
A decision rule
Pull two weeks of utilization for every ECS service from Container Insights and put reservation next to usage. Then ask three questions.
- Across the fleet, is reserved CPU more than five times what is used? If so, packing alone will pay for a good part of the migration.
- Do engineers wait on the platform team for environments, secrets, or deployments? If so, the self-service that comes with a cluster is worth more than the compute savings.
- Is there someone who will own the cluster's upgrade and patch cadence? If not, fix that before you start, or stay on ECS.
Two yes answers out of the first two, plus a yes on the third, and the migration is worth doing. Otherwise spend an afternoon right-sizing the ECS tasks instead. That is real money too, and it does not need a new platform.
What went wrong for us, so it does not for you
- The vertical and horizontal autoscalers must not both act on CPU. On a launch day the VPA rewrote a web pod's request from 400 millicores to 23, the HPA divided by 23, and the fleet flapped between 6 and 12 pods on no load.
- Balloon pods, the usual trick for warm capacity, do not work with Karpenter. It provisions a fresh node faster than the scheduler can evict the balloon, so the real pod lands cold every time.
- Spot is a default, not a rule. Etcd, a Postgres writer under load, a registry's garbage collector, and a search index rebuild each moved to on-demand after an interruption cost more than the discount saved.
- Consolidation does not clean up after a rollout by itself. A 5 percent disruption budget on a 26-node cluster is one node at a time.
Migration order
Move stateless queue workers first. They tolerate interruption, they are usually the most over-reserved, and they let you prove the platform without a user-facing change. Web services follow, behind a canary at the load balancer. Leave managed databases where they are; there is no packing win in moving a database, and there is plenty of risk.
The full story
The measured numbers above come from one client's migration, still in progress. The ECS to EKS case study covers the capacity side, and the GitOps platform case study covers what had to exist before the first service moved.
Paying for reservations you never use?
We'll pull your ECS reservation and utilization numbers for the last two weeks and show you what the same fleet would need on a packed cluster, and whether the migration is worth it for you.