Right-Sizing Task CPU & Memory
Stop guessing task sizes — use real utilization to pick CPU and memory that fit, then leave a sensible headroom.
The single biggest source of wasted ECS spend is over-provisioned tasks. Someone once picked 1024 CPU and 2048 memory because it felt safe, and three years later nobody has questioned it. Right-sizing is the highest-leverage habit you can build.
How I approach it
- Turn on Container Insights so you get per-task CPU and memory utilization, not just cluster averages.
- Look at the p95 of memory usage over a busy week — not the average, and not the single peak.
- Set the task memory to roughly p95 plus 20–30% headroom. Memory is a hard limit: cross it and the kernel OOM-kills your container, so you want a real buffer here.
- CPU is a softer, burstable resource, so you can run it closer to the bone. Size CPU near p95 and let brief spikes borrow idle cycles.
The Fargate wrinkle
Fargate only accepts fixed CPU/memory pairs, so you cannot dial in an arbitrary number. Round to the nearest valid pair — and remember that CPU choice constrains the memory range, so sometimes the cheapest legal fit is one notch up on CPU.
Memory: size for safety. CPU: size for cost. They are not symmetric, and treating them the same is how you either burn money or get OOM-killed at 3am.
Example
{
"family": "api",
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "512",
"memory": "1024",
"containerDefinitions": [
{
"name": "api",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/api:42",
"essential": true,
"memoryReservation": 768,
"portMappings": [
{
"containerPort": 8080
}
]
}
]
}When to use it
- A FinOps team uses Container Insights CPU and memory utilization data to downsize 30% of services that were provisioned with default values and never profiled.
- A developer sets task CPU to 256 and memory to 512 after load-testing confirms the container peaks at 180 MB and 15% of one vCPU under production traffic.
- An SRE adds 20% headroom above the P99 memory peak so tasks are never OOM-killed during traffic spikes while still avoiding gross over-provisioning.
More examples
Fetch P99 Memory for a Service
Retrieves the daily peak memory for the last 7 days so you can size the task memory limit to P99 peak plus a 20% buffer.
aws cloudwatch get-metric-statistics \
--namespace ECS/ContainerInsights \
--metric-name MemoryUtilized \
--dimensions Name=ClusterName,Value=prod Name=ServiceName,Value=api \
--start-time $(date -u -d '-7 days' +%FT%TZ) \
--end-time $(date -u +%FT%TZ) \
--period 86400 \
--statistics Maximum \
--output tableUpdate Task Memory After Right-Sizing
Reads the current task definition and re-registers it with only the memory field updated to the right-sized value.
# Fetch current task def and change only memory
CURRENT=$(aws ecs describe-task-definition \
--task-definition api \
--query 'taskDefinition' --output json)
echo "$CURRENT" | jq '.memory = "768"' | \
aws ecs register-task-definition --cli-input-json file:///dev/stdinEnable Container Insights for Sizing Data
Enables Container Insights so per-container CPU and memory metrics become available for right-sizing analysis.
aws ecs update-cluster-settings \
--cluster prod \
--settings name=containerInsights,value=enabled
echo "Container Insights enabled — check CloudWatch in ~5 minutes"
Discussion