Ask whether to run ECS on EC2 or Fargate for a small, variable workload — a blog post, a comparison table, even Claude Code — and the answer comes back Fargate.
Hosts are work. Someone sizes the fleet, tunes the scaling, patches the AMI, drains instances, and is there when capacity runs out. Paying AWS to make all of that disappear was usually the honest choice.
Then agent workloads changed the math on me. A session can stay warm for hours while doing very little, and Fargate bills for the CPU and memory reserved for it the entire time. On EC2, those sessions can share a host and borrow the same spare headroom. The old answer still removes the old work. The question is whether that work still costs what it used to.
The premium was never compute
Fargate and EC2 charge for different things.
With Fargate, the unit is a task. You choose its CPU and memory, and AWS bills those resources while the task runs; each task gets its own isolated slice of infrastructure. It's clean, predictable, and hard to use efficiently when a task spends most of its life waiting.
With EC2, the unit is the host. You pay for the instance whether it holds one task or twenty. A badly packed instance is expensive empty space; a well-packed one lets many tasks share the same headroom:
Fig. 1 — What each model bills. Fargate meters each task's reservation; EC2 meters the host, and tasks share its headroom.
That makes any simple price comparison incomplete. Fargate can come out cheaper when traffic is small, irregular, or short-lived; EC2 gets interesting once the workload is steady enough to pack. Region, architecture, instance family, and commitment all move the line — and Compute Savings Plans apply to both, so discounts don't settle it either. The useful comparison is the best realistic version of each architecture, including the waste each one creates.
The premium buys something more valuable than compute. It buys the absence of a fleet.
The workload changed
Monocle Craft doesn't run one container per request. It runs one container per session. A session holds live state and work that can't share a sandbox, and it stays open across many turns because the next response has to feel immediate. Most of that time is quiet — the container waits for the user, then wakes up and works hard for a moment.
That shape is awkward for Fargate. Every running task has dedicated capacity, so one quiet session can't use the CPU or memory left idle by another. Size each task for its busiest moment and the idle time gets expensive; size it too tightly and the busy moment gets fragile.
EC2 exposes another option: reserve memory near a session's normal footprint, keep a higher hard limit, and let CPU be shared when the host has room. Ten quiet sessions can sit beside one another, then borrow from the same spare capacity as they wake.
The word doing the work there is can. Shared headroom isn't free capacity — if every session bursts at once, the host still runs out. Reservations need hard limits, utilization needs measurement, and concurrency needs a failure model. Fargate makes the boundary firm. EC2 lets you choose where to draw it.
Packing is only half
Once tasks can share a host, ECS still has to decide where each new task belongs. A memory-based binpack strategy places each new task on the instance with the least remaining memory that can still fit it:
"placementStrategy": [
{ "type": "binpack", "field": "memory" }
]The goal is density: keep busy hosts busy, and leave the others empty enough to disappear later. But binpack doesn't remove an instance — it only chooses among the instances that already exist — and it never moves a running session. It affects the next placement, not the last one.
Capacity is a different layer. A capacity provider connects the cluster to an Auto Scaling group: when tasks can't be placed, managed scaling asks for more instances; when an instance goes empty, it can be released:
Fig. 2 — Placement picks a host for a task; scaling changes how many hosts exist. Nothing moves a running task.
So the saving appears gradually, not by force. New sessions concentrate on the busier hosts, old sessions end where they are, and a lightly used host eventually drains empty — only then is it safe to remove:
A live session never hops hosts to improve the packing. Consolidation happens through future placement and natural endings.
The loop should tend the autoscaler
EC2 still leaves a system to operate. The Auto Scaling group needs bounds. Reservations drift as the product changes; a new model can alter memory use; an AMI that was current last month eventually isn't.
This is where AI helps — but not by becoming a second autoscaler. AWS already has mechanisms for placing tasks, adding capacity, protecting instances, and draining them. Replacing those with an agent trades a predictable control system for a creative one, and creativity is not what scale-in needs.
The loop belongs one level up: comparing reservations with observed usage and flagging drift, watching pending tasks and idle hosts, tracking AMI age and proposing a replacement cadence, noticing when a drain has stalled, explaining why a task couldn't be placed before anyone opens five dashboards.
Its authority should be narrower than its attention. It may observe the whole system while changing only bounded values — a reservation change goes through review, a scale-in requires an empty host, a fleet adjustment moves one step at a time and stops when an invariant breaks:
The loop should not be the autoscaler. It should tend the autoscaler. That's still meaningful: much of the cost of owning EC2 was never writing the Auto Scaling group. It was the small checks around it — whether the settings still matched reality, whether patching was current, whether yesterday's assumptions had quietly expired. A person had to remember to look. A loop doesn't.
The choice is no longer binary
There's now a third answer between Fargate and a fleet you run yourself. ECS Managed Instances packs multiple tasks onto EC2 instances while AWS handles selection, provisioning, scaling, and patching. It keeps most of EC2's packing advantage without handing you the host lifecycle — worth evaluating before building any loop. The constraints are real, though: AWS controls the instances, custom AMIs and host access are out, and instances get replaced on a cadence. A session that can't survive planned replacement may not fit.
So the decision looks closer to this:
- Fargate: when tasks are short-lived, traffic is irregular, scale-to-zero matters, or isolation is worth more than shared headroom.
- Managed Instances: when you want the packing without operating the fleet.
- EC2 with your own Auto Scaling group: when the workload is dense and sustained, host-level control matters, or long-running sessions make managed replacement awkward.
Spot capacity is a separate decision, not a fourth architecture — use it only where interruption is an expected event. A warm session needs checkpointing before a discount makes termination acceptable.
Revisit the default
Fargate didn't get worse. The workload changed. Long-lived sessions make reserved idle capacity easy to see, and better automation makes the alternative cheaper to tend. That moves the line; it doesn't erase it. Shared hosts introduce contention, scaling introduces lag, and every new control surface is another way to be wrong. The host is an asset only when its headroom is useful and its failure modes are understood. Otherwise it's still work, and Fargate's premium is still buying something real.
The old default was priced for a world where every host needed someone's attention. That world is changing. The host isn't disappearing — but the job attached to it is getting smaller.