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.
by OhSudo ·
Your container stopped, and Docker shows this:
$ docker ps -aCONTAINER ID IMAGE COMMAND STATUS NAMES3f1c2a9e7b10 myapp:latest "docker-entrypoint.s…" Exited (137) 2 minutes ago apior, with Docker Compose:
api-1 exited with code 137137 = 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:
- The kernel’s OOM killer: the container used more memory than it was allowed (or than the machine had).
docker stopran 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
docker inspect -f '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' apidocker stats --no-stream # memory use vs. limit, right nowsudo journalctl -k | grep -iE 'out of memory|oom' # what the kernel killed, and whenOOMKilled=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
docker inspect -f '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} {{.State.FinishedAt}}' api137 OOMKilled=true 2026-11-01T09:41:07.512ZThen 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:
docker logs --tail 50 api2. 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:
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:
docker stats apiCONTAINER 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: 1gWith 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.jsfor 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:
- Docker sends
SIGTERMto the main process (PID 1 in the container). - It waits 10 seconds.
- 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:
time docker stop apiThe 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.jsThe 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 --initThe 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 onceAlso 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-stoppedThe 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.