How to Fix Docker Container Exiting With Exit Code 137 (OOMKilled) on AWS ECS
Quick answer
Your container running on ECS dies unexpectedly, and checking its exit status reveals code 137 with an OOMKilled reason β the kernel forcibly terminated it for...
Your container running on ECS dies unexpectedly, and checking its exit status reveals code 137 with an OOMKilled reason β the kernel forcibly terminated it for using more memory than it was allowed. This is a hard resource limit being enforced, not a crash from within your application, and the fix requires understanding exactly how much memory your process actually needs versus what it's been given.
The Problem
A task that was running fine suddenly stops, and checking its status in the ECS console or CLI shows the telltale signature:
$ aws ecs describe-tasks --cluster my-cluster --tasks <task-id>
"containers": [
{
"exitCode": 137,
"reason": "OutOfMemoryError: Container killed due to memory usage",
"lastStatus": "STOPPED"
}
]
Checking container-level logs (if any were flushed before the kill) often shows nothing useful at all, since the process is terminated abruptly with SIGKILL rather than given a chance to log a clean shutdown message.
Why It Happens
Exit code 137 specifically means the process received signal 9 (SIGKILL, 128 + 9 = 137), and on ECS, the most common source of that signal is the Linux kernel's out-of-memory killer, triggered when a container exceeds its configured memory limit. This has two distinct root causes that need different fixes:
- The memory limit is genuinely too low for what the application legitimately needs β a reasonable, healthy process simply outgrew a limit that was set too conservatively.
- A memory leak or unbounded growth in the application itself β memory usage climbs over time (or under specific load patterns) well beyond what a healthy process of that type should ever need, and no static limit would ever be quite high enough to fully prevent an eventual kill.
Distinguishing between these matters, since raising the limit alone only fixes the first case β a genuine leak will eventually consume whatever ceiling you set.
The Fix
First, check your current task definition's memory configuration and compare it against actual observed usage in CloudWatch:
aws ecs describe-task-definition --task-definition my-task --query 'taskDefinition.containerDefinitions[0].memory'
Check the container's actual memory utilization metric in CloudWatch over the period leading up to the kill:
aws cloudwatch get-metric-statistics \
--namespace ECS/ContainerInsights \
--metric-name MemoryUtilized \
--dimensions Name=ClusterName,Value=my-cluster Name=ServiceName,Value=my-service \
--start-time 2026-08-07T00:00:00Z --end-time 2026-08-07T12:00:00Z \
--period 300 --statistics Maximum
If usage climbs steadily over time rather than staying stable under steady load, that's the signature of a leak rather than simply needing more headroom β raising the limit will only delay the same crash. If usage is stable but consistently close to the limit under normal, healthy operation, the limit itself is likely just too conservative:
{
"containerDefinitions": [{
"memory": 1024,
"memoryReservation": 768
}]
}
memory is the hard limit that triggers the OOM kill when exceeded; memoryReservation is a soft reservation ECS uses for scheduling. Setting a reasonable gap between them gives the container headroom for normal memory fluctuation without immediately hitting the hard kill threshold on brief spikes.
For diagnosing an actual leak, add memory profiling to your application in a staging environment where you can safely reproduce sustained load and watch usage over time β the specific tooling depends on your language, but the goal is the same: identify what's accumulating and never getting released.
Still Not Working?
If you've confirmed it's a genuine leak rather than just needing more memory, and can't immediately fix the underlying cause, consider adding a periodic, controlled restart as a stopgap while you investigate β many production systems use a scheduled or usage-triggered restart to bound the blast radius of a slow leak without waiting for an uncontrolled OOM kill:
{
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 30,
"retries": 3,
"startPeriod": 60
}
}
Pair this with an internal memory threshold check in your health endpoint that intentionally fails the check (triggering an ECS-managed restart) before memory usage reaches the hard OOM kill point β this gives your application a chance to log a clean shutdown and gives ECS a graceful replacement cycle, rather than relying entirely on the kernel's abrupt SIGKILL as your only safety net.