Fargate vs EC2: Cost & Latency Tradeoffs
How to choose between Fargate and EC2 by looking honestly at cost, scaling speed, and startup latency.
Almost every ECS decision eventually circles back to one question: Fargate or EC2? After running both in production for years, my honest take is that the answer is rarely about technology and almost always about where your team wants to spend its attention.
The real tradeoff
Fargate charges you per vCPU and per GB of memory, per second, for exactly what each task requests. That is beautifully simple, but you pay a premium for never touching a host. EC2 flips it: you rent whole instances and try to pack them densely, so at steady, high utilization it is meaningfully cheaper — often 30–50% — but only if you actually keep those instances busy.
| Dimension | Fargate | EC2 |
|---|---|---|
| Cost at steady high load | Higher (convenience premium) | Lower if densely packed |
| Cost when idle/spiky | Lower (pay per task) | You pay for idle instances |
| Task start latency | Tens of seconds (ENI + image pull) | Fast if a warm host has room |
| Ops burden | Near zero | Patching, AMIs, scaling |
My rule of thumb
Start on Fargate. Only move a workload to EC2 once it is large enough and steady enough that the monthly bill genuinely bothers you and you can keep instances above ~70% utilization. Latency-sensitive services that scale often benefit from a small pool of warm EC2 capacity, because a task landing on an existing host skips the ENI-provisioning wait that Fargate always pays.
Do not chase the theoretical EC2 discount if it costs you an engineer's week every month. That engineer is more expensive than the compute.
Example
# Compare the two before committing. Pull 30 days of service CPU utilization
# so the Fargate-vs-EC2 decision is based on data, not gut feel.
aws cloudwatch get-metric-statistics \
--namespace AWS/ECS \
--metric-name CPUUtilization \
--dimensions Name=ClusterName,Value=prod Name=ServiceName,Value=web \
--start-time "$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 86400 \
--statistics Average MaximumWhen to use it
- A startup with spiky, unpredictable traffic picks Fargate to avoid paying for idle EC2 capacity between usage bursts.
- A high-throughput ML platform chooses EC2 with Reserved Instances because their tasks run 24/7, making the effective per-hour cost 40% lower than equivalent Fargate.
- A team needing GPU instances for inference moves from Fargate to EC2 launch type because Fargate does not offer GPU instance types.
More examples
Compare Fargate vs Reserved EC2 Cost
Shows the arithmetic behind comparing Fargate and EC2 Reserved pricing so teams can make a data-driven launch-type decision.
# Fargate: 1 vCPU, 2 GB, 730 hours/month (us-east-1)
# vCPU: 1 * 730 * 0.04048 = $29.55
# Mem: 2 * 730 * 0.004445 = $6.49
# Total Fargate: ~$36.04/month
# t3.small On-Demand (2 vCPU, 2 GB): $0.0208/hr => $15.18/month
# t3.small 1yr Reserved: ~$0.013/hr => $9.49/month
# (EC2 wins for steady 24/7 workloads)
python3 -c "print(f'Fargate: \${1*730*0.04048 + 2*730*0.004445:.2f}/mo')"Check Fargate Spot Availability
Runs a task with Fargate Spot as the primary provider and standard Fargate as the fallback, balancing cost savings with availability.
aws ecs run-task \
--cluster demo \
--capacity-provider-strategy \
'[{"capacityProvider":"FARGATE_SPOT","weight":3},{"capacityProvider":"FARGATE","weight":1}]' \
--task-definition worker:2 \
--network-configuration \
'awsvpcConfiguration={subnets=[subnet-a],securityGroups=[sg-w],assignPublicIp=DISABLED}'Measure Task Startup Latency
Times how long Fargate takes to go from run-task to RUNNING state — useful data for deciding between Fargate and pre-warmed EC2 instances.
START=$(date +%s%3N)
TASK=$(aws ecs run-task \
--cluster demo --launch-type FARGATE \
--task-definition probe:1 \
--network-configuration 'awsvpcConfiguration={subnets=[subnet-a],securityGroups=[sg-p],assignPublicIp=DISABLED}' \
--query 'tasks[0].taskArn' --output text)
aws ecs wait tasks-running --cluster demo --tasks "$TASK"
END=$(date +%s%3N)
echo "Cold start: $((END - START)) ms"
Discussion