Docker Exit Code 137 (OOMKilled): Causes and Fixes
Docker exit code 137 means the container's main process was terminated by signal 9 (SIGKILL). The math is simple: 128 + 9 = 137. In practice this almost always means the Linux out-of-memory (OOM) killer reclaimed memory from a container that exceeded its limit, though a manual docker kill produces the same code. Check docker inspect for "OOMKilled": true to tell the two apart.
What Docker exit code 137 actually is
Container exit codes above 128 encode a Unix signal. The formula is 128 + signal number. Signal 9 is SIGKILL, so 128 + 9 = 137. SIGKILL cannot be caught, blocked, or ignored by the process. The kernel terminates it immediately, which is why you rarely get a clean shutdown log line before a 137.
There are two common ways a process gets SIGKILL inside Docker:
- The kernel's OOM killer fires because the container hit its memory limit (or the host ran out of RAM).
- Something sent an explicit kill, for example
docker kill,docker stoptiming out and escalating to SIGKILL, or an orchestrator like Kubernetes or a systemd unit deciding the process took too long to stop.
Both land on 137. The OOMKilled flag is what separates "you ran out of memory" from "someone or something killed it on purpose."
Confirm whether it was an OOM kill
Start with inspect. This is the single most useful check:
docker inspect <container> --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
If that prints true 137, the OOM killer got it. If it prints false 137, the SIGKILL came from somewhere else (a stop timeout, a manual kill, or the host OOM killer acting on a process without a per-container cgroup limit, which does not always set the flag).
For the full state block:
docker inspect <container> --format '{{json .State}}' | python3 -m json.tool
Then check the host kernel log. The OOM killer writes there even when Docker's flag is ambiguous:
sudo dmesg -T | grep -iE 'killed process|out of memory|oom'
You are looking for lines like Out of memory: Killed process 12345 (node) total-vm:... anon-rss:.... That tells you which process died and roughly how much resident memory it held when the kernel gave up. On systemd hosts, journalctl -k | grep -i oom gives the same information.
The common causes, ranked by how often I hit them
1. The container's memory limit is too low
If you ran with --memory (or mem_limit in Compose, or resources.limits.memory in Kubernetes) and the app's real working set is larger than that number, the cgroup OOM killer fires the moment usage crosses the limit. This is the cleanest case: OOMKilled is true, and the fix is either raise the limit or shrink the app.
See what you actually set:
docker inspect <container> --format 'limit={{.HostConfig.Memory}} swap={{.HostConfig.MemorySwap}}'
A value of 0 means no limit, in which case the container can use all host RAM and any OOM kill is a host-level event instead.
2. The host ran out of RAM
No per-container limit, but the box itself is out of memory. The kernel then picks a victim by OOM score, and it is often (but not always) the biggest memory hog. Your container can die because a completely different process on the host went wild. free -m and the dmesg output above tell you if the whole host was under pressure.
3. A real memory leak or an unbounded workload
The limit is reasonable, but the app climbs steadily until it hits the ceiling, gets killed, restarts, and climbs again. A sawtooth memory graph is the giveaway. Common culprits: a cache with no eviction, an in-memory queue that grows faster than it drains, a JVM or Node heap that was never capped, or a request handler that buffers an entire upload into memory.
4. A docker stop that escalated
By default docker stop sends SIGTERM, waits 10 seconds, then sends SIGKILL. If your app ignores SIGTERM or takes longer than the grace period to shut down, it gets a 137 that has nothing to do with memory. Here OOMKilled is false and dmesg is silent. Fix the signal handling or extend the timeout with docker stop -t 30.
How to fix it
Watch memory before it dies
You cannot fix what you cannot see. Live view:
docker stats <container>
The MEM USAGE / LIMIT column shows how close you are to the ceiling in real time. If usage sits at 98% of the limit and climbs, the next 137 is coming. For a deeper walkthrough of the columns and what they mean, see The docker stats Command: A Complete Guide, and for the broader picture of tracking container health over time, Docker Container Monitoring: A Complete Guide.
Set a sane limit, then tune it
Run with an explicit limit so a runaway container cannot take the host down with it:
docker run -m 512m --memory-swap 512m my-image
Setting --memory-swap equal to --memory disables swap for the container, which makes OOM behavior predictable. Watch docker stats under real load and set the limit a bit above the observed peak, not at it. Garbage-collected runtimes spike during collection, so leave headroom.
In Compose:
services:
api:
image: my-image
deploy:
resources:
limits:
memory: 512M
Cap the runtime's heap, not just the container
The container limit is a hard wall. The runtime needs its own limit set below that wall so it can manage memory gracefully instead of getting SIGKILLed mid-allocation.
Node:
node --max-old-space-size=384 server.js
JVM (modern JDKs read cgroup limits automatically, but you can be explicit):
java -XX:MaxRAMPercentage=75 -jar app.jar
The goal is to let the runtime throw an out-of-memory error it can log, rather than have the kernel kill it with no trace.
Handle SIGTERM properly
If your 137s come from stop timeouts, make the app shut down cleanly on SIGTERM and make sure PID 1 forwards signals. Running your app directly (not wrapped in a shell that swallows signals) or adding --init to docker run fixes the most common variant, where a shell as PID 1 never passes the signal to your process.
Get told the moment it happens
The worst version of a 137 is the one you find out about from a customer. A container OOMs at 3am, the restart policy brings it back, and by the time you look everything is green again with no obvious reason for the gap in your logs. This is where remote monitoring earns its keep. Docker HQ watches your containers over SSH and sends a push notification when one exits or restarts, so you can check docker inspect and dmesg while the evidence is still fresh instead of guessing after the fact. If you want the setup details, we wrote up how to get alerted when a Docker container goes down. You can see what host and alert limits look like on the pricing page.
Frequently asked questions
Does exit code 137 always mean out of memory?
No. 137 means the process received SIGKILL. OOM is the most common cause, but a manual docker kill, a docker stop that exceeded its grace period, or an orchestrator killing the pod all produce 137 too. Check docker inspect --format '{{.State.OOMKilled}}' and dmesg to confirm memory was the reason.
What is the difference between exit code 137 and 143?
Both are signal-based. 143 is 128 + 15, meaning SIGTERM, the polite "please shut down" signal that a process can catch and handle. 137 is 128 + 9, SIGKILL, which cannot be caught. A clean docker stop on a well-behaved app gives 143; when that app ignores the SIGTERM and gets force-killed after the timeout, you see 137 instead.
How do I stop a container from being OOMKilled?
Give it enough memory and stop it from needing more than it should. Set a container limit above the real peak usage you measure with docker stats, cap the runtime's heap below that limit, and fix any actual leak so usage does not climb forever. If the host itself is out of RAM, add memory or move workloads off the box.
Where does Docker log the OOM kill?
Docker records it in the container state (OOMKilled: true), but the detailed record is in the host kernel ring buffer. Run sudo dmesg -T | grep -i oom or journalctl -k | grep -i oom to see which process the kernel killed and how much memory it was using at the time.
Once you have confirmed an OOM kill, the next move is measurement: run docker stats under production-like load, find the real peak, and set your limit and heap size from that number instead of a guess.