Skip to content
ohsudo
Docker & Containers

Docker "exited with code 137": OOM kill or a stop that took too long

Exit code 137 means the container was killed with SIGKILL. Find out if it ran out of memory or ignored docker stop, and fix each case.

Intermediate5 min readApplies to Docker Engine 24+ · Docker Compose v2 · Linux hosts · Docker Desktop

by OhSudo ·

Your container stopped, and Docker shows this:

$ docker ps -a
CONTAINER ID IMAGE COMMAND STATUS NAMES
3f1c2a9e7b10 myapp:latest "docker-entrypoint.s…" Exited (137) 2 minutes ago api

or, with Docker Compose:

api-1 exited with code 137

137 = 128 + 9. When a process is killed by a signal, its exit code is 128 plus the signal number, and signal 9 is SIGKILL. So the program didn’t crash and didn’t exit on its own: something killed it, and it had no chance to clean up. There are two usual suspects:

  1. The kernel’s OOM killer: the container used more memory than it was allowed (or than the machine had).
  2. docker stop ran out of patience: Docker asked the app to stop, the app didn’t, and after 10 seconds Docker killed it.

One command tells them apart.

TL;DR

Terminal window
docker inspect -f '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' api
docker stats --no-stream # memory use vs. limit, right now
sudo journalctl -k | grep -iE 'out of memory|oom' # what the kernel killed, and when

OOMKilled=true → section 2 (memory). OOMKilled=false → section 3 (stop/kill) — or section 2, if the host itself ran out of memory.

1. Ask Docker why the container stopped

Terminal window
docker inspect -f '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} {{.State.FinishedAt}}' api
137 OOMKilled=true 2026-11-01T09:41:07.512Z

Then look at the container’s last lines of output. An OOM kill is sudden, so the log usually just stops in the middle of normal work, with no error:

Terminal window
docker logs --tail 50 api

2. Out of memory (OOMKilled=true)

The container has a memory limit (docker run -m, mem_limit or deploy.resources.limits.memory in Compose). When the processes inside go over it, the kernel kills one of them. The kernel log has the details:

Terminal window
sudo journalctl -k --since "1 hour ago" | grep -iE 'out of memory|oom'
Memory cgroup out of memory: Killed process 48211 (node) total-vm:2210432kB, anon-rss:519812kB, ...

anon-rss is how much memory the process really used when it was killed. Compare it with the limit, and watch the container while it runs:

Terminal window
docker stats api
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM %
3f1c2a9e7b10 api 4.12% 498.3MiB / 512MiB 97.32%

Now decide which of the two is wrong — the limit or the app.

The limit is too low for the work. Raise it:

services:
api:
image: myapp:latest
deploy:
resources:
limits:
memory: 1g

With docker run, use -m 1g. Then recreate the container (docker compose up -d) — a limit doesn’t change on a running container.

The app uses more than it should. A bigger limit only delays the next kill if memory keeps growing. Check the runtime’s own memory settings — many runtimes size their memory from the whole machine, not from the container:

  • Java: since Java 10 (and 8u191) the JVM respects the container limit. Set the heap as a share of it: -XX:MaxRAMPercentage=75.
  • Node.js: limit the V8 heap below the container limit, for example node --max-old-space-size=384 app.js for a 512 MB container.
  • Many workers (Gunicorn, PHP-FPM, Puma…): every worker is a full process. Fewer workers, or more memory.

If docker stats shows memory growing all the time under steady traffic, it’s probably a leak in the application — a bigger limit won’t fix that.

Note

No limit set, and still OOM? Without a limit, the container can use all the memory of the host — and when the host runs out, the kernel kills whatever it finds best, maybe your container. Then OOMKilled can be false, but the kernel log still shows Out of memory: Killed process …. On Docker Desktop, “the host” is a small virtual machine: its memory is set under Settings → Resources.

3. Killed by docker stop (OOMKilled=false)

docker stop, docker compose down and every redeploy stop containers like this:

  1. Docker sends SIGTERM to the main process (PID 1 in the container).
  2. It waits 10 seconds.
  3. If the container is still running, it sends SIGKILL → exit code 137.

So if every stop or deploy ends in 137 and takes exactly ~10 seconds, your app never receives or never handles SIGTERM. Time it:

Terminal window
time docker stop api

The usual causes:

Shell form in the Dockerfile. With CMD node server.js (no brackets), Docker runs /bin/sh -c "node server.js". The shell becomes PID 1, gets the SIGTERM, and doesn’t pass it to your app. Use the exec form, so your app is PID 1:

CMD ["node", "server.js"]

An entrypoint script. If a shell script starts your app, end it with exec, so the app replaces the shell instead of running as its child:

#!/bin/sh
# ... setup ...
exec node server.js

The app ignores SIGTERM. A process running as PID 1 gets no default signal handling from the kernel: if the program doesn’t handle SIGTERM itself, nothing happens. Either handle it in the app (close connections, then exit), or put a small init process in front of it that does:

services:
api:
init: true # same as docker run --init

The app needs more than 10 seconds to finish its work (long requests, queue jobs). Give it more time:

services:
api:
stop_grace_period: 30s # docker stop -t 30 does the same once

Also check that nothing else kills the container: docker kill sends SIGKILL by default, and so can monitoring scripts or orchestrators. docker events --since 1h --filter container=api shows what happened to it and when.

4. Keep the service up while you fix it

A restart policy brings a killed container back on its own. It doesn’t fix the cause, but it keeps the service running while you look for it:

services:
api:
restart: unless-stopped

The Docker Compose Generator adds a restart policy and healthchecks to every service it creates. Then check docker inspect -f '{{.RestartCount}}' api from time to time — a number that keeps growing means the problem is still there.

Shell, we have a problem!

Similar, but different: 143 (128 + 15) means SIGTERM ended the process: the app had no handler for it, but at least it stopped in time — usually a normal docker stop. An app that handles SIGTERM and shuts down cleanly exits with 0. 139 (128 + 11) is a segmentation fault: the program crashed by itself. 1 or other small numbers are the app’s own error codes — read docker logs. In Kubernetes, the same 137 shows up as Last State: Terminated, Reason: OOMKilled, Exit Code: 137.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles