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 start as PID 1 that does not forward signals; use node directly 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.

All guides