Docker Exit Code 137 With Node.js: OOM Kill Fix Guide
Container exits with code 137 or Kubernetes shows OOMKilled? Confirm the OOM kill, set Node's heap below the container limit, and find what is eating memory.
Exit code 137 means your container's main process was killed with SIGKILL (128 + 9), and in most Node.js cases the cause is the kernel's out-of-memory killer because the container hit its memory limit; confirm it, set Node's heap size below that limit, then fix whatever is using the memory.
Your app does not log anything before dying, because SIGKILL cannot be caught. That is why this one feels like a ghost bug.
Step 1: Confirm It Was OOM
docker inspect <container> --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
# true 137 -> out of memory
On Kubernetes:
kubectl describe pod <pod> | grep -A5 "Last State"
# Reason: OOMKilled Exit Code: 137
Exit 137 with OOMKilled: false can also come from a manual docker kill, a platform eviction, or a failed health check that triggered a restart. Rule out those before changing memory settings.
Step 2: Understand the Two Limits
| Limit | What it controls | Who enforces it |
|---|---|---|
Container memory (--memory, K8s limits.memory) |
Total RSS of the container | Linux kernel, kills at the limit |
Node heap (--max-old-space-size) |
V8 JavaScript heap only | V8, throws "heap out of memory" |
Node's RSS includes more than the heap: buffers, native modules, thread stacks, and compiled code. If you set the heap equal to the container limit, the process dies from the kernel before V8 ever complains.
Step 3: Set Heap Below the Container Limit
A common starting point is roughly 75% of the container limit, adjusted for your workload:
# container limit: 1 GB
ENV NODE_OPTIONS="--max-old-space-size=768"
CMD ["node", "server.js"]
Recent Node versions pay some attention to cgroup limits when choosing the default heap, but behavior differs across versions and setups, so set it explicitly and test.
Step 4: Find What Is Using Memory
Raising the limit only delays the crash if memory grows without bound. Log memory over time:
setInterval(() => {
const m = process.memoryUsage()
logger.info({
rssMb: Math.round(m.rss / 1e6),
heapUsedMb: Math.round(m.heapUsed / 1e6),
externalMb: Math.round(m.external / 1e6),
}, 'mem')
}, 30_000).unref()
| Pattern | Likely cause |
|---|---|
heapUsed climbs steadily |
JavaScript leak; see Node.js memory leaks |
rss climbs, heapUsed flat |
Buffers, native addons, large uploads in memory |
| Spikes at specific requests | Loading big files or result sets into memory; stream instead |
| Spikes at build time | next build needs more memory; raise the limit for CI only |
Common Container Mistakes
- No memory limit set on the host, so the host's OOM killer chooses your process at random.
- Multiple workers (cluster mode or several processes) each consuming a full heap inside one container limit.
- Build and runtime in one image with a limit sized for runtime only.
npm startas PID 1 that does not forward signals; usenodedirectly or a tiny init.
Make the Crash Observable
A container restart loop is a classic hidden incident. Alert on restart count, OOMKilled events, and memory at 80% of the limit. For deployment-side monitoring, see Render and Railway monitoring, and pair it with the Node.js production error handling guide. Processes under memory pressure also drop connections, which surfaces as ECONNRESET in other services.
Let Relia Connect the Crash to the Cause
Relia watches your servers' CPU, memory and disk alongside errors, so a restart loop shows up as an incident write-up with the likely cause and one next step. On request it writes a fix plan, such as streaming a large response or bounding a cache, with a "Copy for your AI assistant" button. Relia doesn't change code itself, and after you ship it checks the fix against live traffic.
FAQ
What does exit code 137 mean in Docker?
The process received SIGKILL, commonly from the out-of-memory killer. 137 equals 128 plus signal 9.
How do I know it was an OOM kill and not something else?
Check docker inspect for OOMKilled: true, or kubectl describe pod for Reason: OOMKilled.
Should I just increase the memory limit?
Only as a stopgap. If memory grows over time, you have a leak that will hit any limit eventually.