Fargate bills per vCPU and per GB of memory reserved in the task definition, for as long as the task runs — not for what the container inside it actually uses. A task definition copied from a bigger service "to be safe," or sized once early and never revisited as the workload changed, bills that reservation continuously regardless of actual utilization.
Reservation, not usage, is what's billed
This is the same shape as the Lambda memory problem, but persistent instead of per-invocation: a task running 24/7 at 1 vCPU / 2 GB when it steadily uses a fraction of that is paying the same rate every hour whether or not anything is happening. Container Insights (or a sidecar metrics setup) will show actual CPU and memory utilization per task, and it's common to find services sized well above their steady-state need.
Task definition request vs. steady-state CloudWatch Container Insights usage for the same service.
Non-production is the easiest win
Staging, dev, and QA environments frequently run the exact same task definition as production, sized for production load, at a fraction of the traffic — and often around the clock even though nobody's working against them outside business hours. Right-sizing those tasks independently, and scaling non-prod services to zero or near-zero outside working hours, is usually the fastest Fargate win available because it requires no application changes at all.
Fargate vs. EC2 isn't a one-time decision
Fargate's per-task premium over the equivalent EC2 capacity buys you not managing instances, patching, or bin-packing — a real and often correctly-made trade for variable or bursty workloads. For a stable set of always-on services with well understood sizing, EC2 (with Cluster Auto Scaling or Capacity Providers) or Fargate Spot for interruption-tolerant workloads can meaningfully undercut steady-state Fargate pricing. Worth periodically re-checking as a service graduates from "new and uncertain" to "stable and predictable."
What to check first
- Container Insights CPU/MemoryUtilization per service, compared against the task definition's reserved values — the gap is the number to size down.
- Whether non-prod task counts and sizes actually differ from production, or were just copied wholesale.
- Whether any interruption-tolerant workloads (queue consumers, batch jobs, CI runners) are running on standard Fargate instead of Fargate Spot.