Fargate CPU and memory are a pair, not two numbers
17 August 2026
AWS ECS Task Definition Analyzer Two roles, and they are not the same. Runs in your browser.Invalid setting for memory. The valid values are ... is the error, and it
arrives on a task definition where the memory looks perfectly reasonable. The
value is not the problem. On Fargate, CPU and memory are a matched pair from a
fixed table, and picking a legal number for one constrains the other.
The API rejects the combination and reports it as a problem with a single field, which sends people to change the value that was fine.
The table
Seven CPU sizes. Each one permits a range of memory, in a fixed step. Nothing outside this is accepted, in any region, on any platform version.
| CPU | vCPU | Memory | Step |
|---|---|---|---|
| 256 | 0.25 | 512 MiB, 1 GB, 2 GB | 512 MiB |
| 512 | 0.5 | 1 GB to 4 GB | 1 GB |
| 1024 | 1 | 2 GB to 8 GB | 1 GB |
| 2048 | 2 | 4 GB to 16 GB | 1 GB |
| 4096 | 4 | 8 GB to 30 GB | 1 GB |
| 8192 | 8 | 16 GB to 60 GB | 4 GB |
| 16384 | 16 | 32 GB to 120 GB | 8 GB |
Checked against the AWS Fargate task definition documentation on 2026-07-30. The 8 and 16 vCPU rows are Linux only and need platform version 1.4.0 or later. Windows containers on Fargate are limited to 1, 2 and 4 vCPU.
Three properties of that table do all the damage:
The ratio is bounded at both ends. Every row allows a minimum of 2 GB per vCPU and a maximum of 8 GB per vCPU, falling to 7.5 GB on the 4, 8 and 16 vCPU rows. You cannot buy 1 vCPU with 16 GB, and you cannot buy 4 vCPU with 2 GB. If your workload is genuinely lopsided, Fargate makes you pay for the other axis to get there.
The step is not always 1 GB. At 8 vCPU it is 4 GB and at 16 vCPU it is 8 GB. A task asking for 8 vCPU and 18 GB is inside the range and still illegal, because 18 is not 16 plus a multiple of 4.
The bottom row is not a sequence at all. 0.25 vCPU takes 512 MiB, 1 GB or 2 GB and nothing else. There is no 1.5 GB, even though the gap between the first two values is 512 MiB and the eye expects the pattern to continue. Every other row is an arithmetic sequence and this one is a list, which is the kind of detail that survives in documentation and gets lost in anything that models it.
The three cases that catch everyone
1 vCPU with 3.5 GB. 3 GB is legal, 3.5 GB is not, because the step is 1 GB and there is no half-gigabyte anywhere in the table above the bottom row.
0.5 vCPU with 512 MiB. 512 MiB is a legal memory value one row up, so it looks like a value the table accepts. The minimum at 512 CPU units is 1 GB. This is the common one, because 0.25 vCPU with 512 MiB is the cheapest legal task and doubling the CPU while leaving the memory alone is the obvious next move.
0.25 vCPU with 1.5 GB. Between two legal values, on what looks like the step, and rejected. This one is worth singling out because it is the case a model gets wrong rather than a person: anything that stores the row as a minimum plus a step accepts it. The tool on this site did exactly that until 2026-08-17, and writing this post is what found it.
How it shows up
Not usually at the console. It shows up in a pipeline, because
RegisterTaskDefinition is what fails and that runs in CI.
The symptom is a deploy that fails on the register step with a message naming
memory while the diff in the pull request changed cpu. Someone widens the
memory, which moves it further from a legal pairing, and the second attempt
fails with the same error and a different valid-values list.
The other appearance is subtler and more expensive. A task sized 4 vCPU and 8 GB because 4 vCPU was needed for a burst, running at 6% CPU for the other 23 hours of the day, because the 8 GB it actually needed forced the CPU up. Look for tasks whose memory sits at the minimum of their row: that is the signature of memory driving the choice and CPU being paid for as a side effect.
The wrong instinct: set memory at the container level instead
Faced with a rejected task size, the reflex is to move the numbers down into
containerDefinitions, where memory and cpu are per-container and no table
applies.
This does not work on Fargate, and it half-works in a way that is worse than
failing. Fargate requires task-level cpu and memory. They are optional on
EC2, where container-level values are used and the instance provides the ceiling.
On Fargate the task size is what gets provisioned, so leaving it out is rejected
outright.
What people end up with is task-level values that satisfy the table plus
container-level values that contradict them. The container-level memory is a
hard limit: exceed it and the container is killed with exit 137, regardless
of how much room the task has. So a task provisioned with 8 GB, running one
container with memory: 2048, is a container that dies at 2 GB while 6 GB sits
unused, and the task-level number is the one everybody looks at.
The related mistake is setting memory when you meant memoryReservation.
memoryReservation is the soft limit used for placement; memory is the hard
kill. A container with a hard limit and no reservation gets no scheduling
consideration and a guaranteed OOM ceiling, which is the opposite of the intent
in most task definitions that carry it.
Choosing a size, with the trade-offs
The table forces a decision. These are the three real options.
| Approach | What it costs | When it is right |
|---|---|---|
| Size to peak | You pay the peak rate 24 hours a day. A 4 vCPU task at 6% average utilisation costs about 16x what the work needs | Latency-critical services where a slow start is a user-visible failure, and single-task workloads with no horizontal option |
| Size to steady state, scale out | Scaling reacts in tens of seconds, so a spike is served slowly before it is served widely. More tasks means more per-task overhead: each one pays its own sidecars, its own JVM heap floor, its own connection pool | Request-driven work with a load balancer in front. This is the default answer for a web service |
| Split the workload | Two task definitions, two deployments, more operational surface | A process that is genuinely two shapes, typically an API that also runs a memory-heavy background job. Splitting lets each half sit on the row that fits it |
The numbers that make this concrete, at published us-east-1 Linux/x86 on-demand rates as of 2026-08: $0.04048 per vCPU-hour and $0.004445 per GB-hour. A 1 vCPU, 2 GB task is about $0.0494 an hour, or $36 a month running continuously. The same task at 4 vCPU and 8 GB is about $144 a month. Rates differ by region and change; price your own before deciding anything.
Two levers that do not require touching the table:
- ARM64. Fargate on Graviton is roughly 20% cheaper for the same size and needs a multi-arch image. For most interpreted and JVM workloads that is a rebuild rather than a port.
- Fargate Spot. Up to 70% off, with a two-minute interruption warning. Linux and x86 only. Correct for queue consumers and batch, wrong for anything that cannot be killed mid-request.
What changed recently
8 and 16 vCPU sizes exist now, which they did not for the first several years of Fargate. If your sizing rule of thumb predates them, the ceiling you remember is wrong: it was 4 vCPU and 30 GB.
Ephemeral storage is configurable. The default is 20 GiB and it can be raised
to 200 GiB with ephemeralStorage. On platform version 1.4.0 and later it is
encrypted with an AWS-owned key by default. Tasks that used to fail on disk with
no obvious cause now have a field to set.
stopTimeout is capped at 120 seconds on Fargate, against 30 seconds by
default. If your container needs longer than two minutes to drain, Fargate
cannot give it to you and the design has to change rather than the number.
Windows on Fargate is limited to 1, 2 and 4 vCPU and does not support Spot.
Fixing a system already running
1. Find the tasks whose memory is at the minimum of their row. Those are the ones where memory forced the CPU up. They are your over-provisioned tasks, and the fix is usually a smaller row plus more replicas rather than a smaller task.
2. Read actual utilisation before resizing, not the task definition. Container Insights gives per-task CPU and memory utilisation. The number that matters for memory is the peak over a full business cycle including deploys, because a rolling deploy briefly doubles memory pressure on the node the way nothing else does.
3. Set memoryReservation on every container, and memory only where you
want a hard kill. In a single-container task, task-level memory is already the
ceiling, so a container-level hard limit adds nothing except a lower one.
4. Change one axis per deploy. Resizing CPU and memory together makes a latency regression impossible to attribute.
5. Check the execution role at the same time. It is unrelated to sizing and it is the other thing that breaks task definitions in CI: the execution role pulls the image and writes logs, the task role is what your application uses to call AWS. A task with one role doing both jobs is over-privileged in one direction and the failure mode when it is missing is a task that never starts.
When to ignore all of this
If the workload runs for ninety seconds a day, sizing is not worth an hour of anyone’s time. A 4 vCPU task that runs briefly on a schedule costs a few cents a month. Right-sizing it is a real cost against an imaginary saving.
If you are near the top of the table, the answer may not be Fargate. At 16 vCPU and 120 GB you are paying a serverless premium for a size where EC2 capacity providers, or EKS, give you bin-packing across tasks that Fargate cannot. Fargate’s economics are best when the task is small and the count varies.
If your workload is genuinely lopsided, the table is a real constraint, not a detail to work around. Something needing 1 vCPU and 40 GB has no legal Fargate size. That is a signal to reach for EC2 rather than to accept 8 vCPU you will never use.
The short version
- CPU and memory are a pair from a fixed table. The error names one value and the pairing is what is wrong.
- Every row allows 2 GB to 7.5 GB of memory per vCPU. Lopsided workloads have no legal size.
- The step is 4 GB at 8 vCPU and 8 GB at 16 vCPU, not 1 GB.
- 0.5 vCPU cannot take 512 MiB. Its minimum is 1 GB.
- Task-level
cpuandmemoryare required on Fargate, unlike EC2. - Container-level
memoryis a hard limit and will kill a container inside a task with plenty of room.memoryReservationis the soft one. - Memory sitting at the minimum of its row means memory chose your CPU, and you are paying for cores you are not using.
The ECS task definition analyzer checks a task
definition against this table and names the pairing rather than the field, along
with the execution and task role split, hard limits with no reservation, a
stopTimeout over the Fargate maximum, and container limits that add up to more
than the task was given. It runs in your browser, which matters here because a
task definition routinely contains environment variable names you would rather
not paste into somebody’s backend.