Quick fix
# 1. Confirm it was OOM
docker ps -a --filter "status=exited" # Exited (137)
docker inspect <container> --format "{{.State.OOMKilled}} {{.State.ExitCode}}"
# true 137 = OOM; false 137 = killed externally
# 2. See actual memory usage
docker stats --no-stream # MEM USAGE / LIMIT columns
# 3. Kernel-side evidence (optional)
dmesg | grep -i "oom-kill" | tail
# compose: set a limit + crash-restart fallback
services:
app:
image: your-image
mem_limit: 512m # or deploy.resources.limits.memory: 512M
restart: unless-stopped # restarts after OOM (but repeated restarts = fix the root cause)
What you will run into
A process inside a container dies mid-run for no visible reason: docker ps -a shows Exited (137), docker inspect reports State.OOMKilled=true;
the service "randomly" restarts or disappears, and the logs are empty (the kernel SIGKILLs the process, so it never gets to log anything).
The container exceeded its cgroup memory limit and the kernel OOM killer took it out — this is not the host running out of RAM, it is the container's own quota running out.
Why this happens
docker run -m 64m (compose mem_limit / deploy.resources.limits.memory) sets a cgroup memory cap;
when the process's cumulative memory (including attributed page cache) exceeds the cap, the kernel OOM killer picks a process inside that cgroup and kills it — the killed container's exit code is 137 (128 + SIGKILL 9).
A container without -m has no memory cap and can eat the whole host, taking other services down with it.
Docker implements the cap with cgroup v2 memory.max (reproduced: inside a -m 128m container, cat /sys/fs/cgroup/memory.max = 134217728).
On OOM the kernel records oom-kill:constraint=CONSTRAINT_MEMCG,...task=python3 (dmesg verbatim, reproduced) — this is the deterministic signature of a cgroup-level OOM, distinct from host-wide memory pressure (constraint=CONSTRAINT_NONE).
Two defaults worth remembering: --memory-swap defaults to --memory × 2 (i.e. the container can use up to 2x its memory limit of swap; -1 = unlimited swap), and docker stats's MEM USAGE / LIMIT columns are the observation entry point.
The troubleshooting chain is deterministic: docker ps -a shows 137 → docker inspect <container> --format "{{.State.OOMKilled}}" confirms → docker stats quantifies usage → dmesg | grep oom gives the kernel record.
Deterministic takeaway: exit 137 is not always OOM (a manual kill -9 also gives 137) — check the OOMKilled field before concluding;
for runtimes with their own memory pools (Java/Python/Node), size the container limit by "process RSS + runtime overhead", not by the app heap config alone.
Step by step
1. Confirm OOMKilled — Must do
docker inspect <container> --format "{{.State.OOMKilled}} {{.State.ExitCode}}"
true 137 → cgroup OOM (this pitfall); false 137 → external kill -9 or platform reclamation, look elsewhere.
2. Quantify memory usage — Must do
docker stats --no-stream <container>
# MEM USAGE / LIMIT: usage close to LIMIT = quota too small
3. Set/adjust the limit — By diagnosis
- No
-mset: give it a sane cap first (-m 512m/ composemem_limit) so one container cannot eat the host; - Limit set but still OOM: raise it, or fix the root cause (step 4);
- Swap:
--memory-swapdefaults to--memory× 2;--memory-swap -1= unlimited swap (be careful: can exhaust host swap).
4. Root cause: the process's memory model — By diagnosis
Java: -Xmx covers the heap only; the container limit must add metaspace/thread stacks/direct memory (common approach: -XX:MaxRAMPercentage=75); Python: large one-shot allocations (bytearray, caches — reproduced: a 256MB allocation dies at a 64m limit);
Node: --max-old-space-size.
Rebuild the container after fixing code/args.
5. Verify — Must do
docker run --rm -m 128m your-image your-app # runs steadily
docker stats --no-stream # usage well below LIMIT
docker inspect <container> --format "{{.State.OOMKilled}}" # false
How to confirm it works
docker ps shows the container steadily Up (no more repeated Exited 137); docker inspect reports OOMKilled=false; docker stats shows headroom below the limit (reproduced: 128m limit, 96MB allocation → 100.1MiB / 128MiB 78.22%).
Other environments
| Scenario | Behavior |
|---|---|
No -m limit | Container can exhaust host RAM and drag down other services (no cap by default) |
-m 64m + 256MB allocation | Process killed: exit 137, OOMKilled=true, dmesg records constraint=CONSTRAINT_MEMCG (reproduced) |
--memory-swap | Defaults to memory × 2; -1 = unlimited swap; larger values buffer peak usage |
| exit 137 but OOMKilled=false | External kill -9 (e.g. platform reclamation), not this pitfall |
Symptom
$ docker ps -a
oomdemo2 ... Exited (137) 4 seconds ago
$ docker inspect oomdemo2 --format "{{.State.OOMKilled}} {{.State.ExitCode}}"
true 137
# kernel side (dmesg excerpt)
oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=docker-...scope,task=python3,pid=24719,uid=0
Memory cgroup out of memory: Killed process 24719 (python3) total-vm:273380kB, anon-rss:64640kB
Signature: container exits 137 with OOMKilled=true; no error in the app log (SIGKILL bypasses the app); docker stats usage is pinned at LIMIT.
Cause
The container's cumulative memory exceeded the cgroup (memory.max) cap, so the kernel OOM killer killed a process inside the cgroup; exit code = 137 (SIGKILL). No -m means no cap. --memory-swap defaults to 2×--memory.
Solution
docker inspectto confirm OOMKilled=true;docker statsto quantify usage;dmesg | grep oom-killfor the kernel record;- Set/raise
-m(composemem_limit); tune--memory-swapif needed; - Fix the root cause per runtime model (Java MaxRAMPercentage / Node max-old-space-size / Python large allocations) and rebuild;
- Verify OOMKilled=false and stats headroom.
Verification Evidence
- Local reproduction (2026-08-31, custom prebuilt WSL, docker 29.1.3):
- Baseline: unlimited container allocates 256MB successfully (
alloc ok 268435456); -m 64m+ 256MB allocation → process killed;docker runexit code 137;docker inspect→OOMKilled=true ExitCode=137;- dmesg verbatim:
oom-kill:constraint=CONSTRAINT_MEMCG,...,task=python3,pid=24719+Memory cgroup out of memory: Killed process 24719 (python3) total-vm:273380kB, anon-rss:64640kB; -m 128mcontainer:cat /sys/fs/cgroup/memory.max= 134217728 (128MiB);docker statsshows100.1MiB / 128MiB 78.22%;--memory-swapdefault = 2×memory per docker help docs.
Evidence details kept locally (2026-08-31)
Related canonicals
- docker system prune cleanup boundaries (same-batch Docker domain resource problems)
- Container timezone / garbled Chinese (same-batch container runtime config pitfalls)
Applicable tools (1)
This problem is linked from the tool pages below — reachable from either side.
dockerSource & evidence
← Coding Agents & Terminal domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk