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.

DimensionFargateEC2
Cost at steady high loadHigher (convenience premium)Lower if densely packed
Cost when idle/spikyLower (pay per task)You pay for idle instances
Task start latencyTens of seconds (ENI + image pull)Fast if a warm host has room
Ops burdenNear zeroPatching, 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

Example · bash
# 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 Maximum

When 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.

Example · bash
# 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.

Example · bash
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.

Example · bash
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

  • Be the first to comment on this lesson.