Start with the signal, not the assumption
Linux convention reports a process killed by signal 9 (SIGKILL) as exit code 128 + 9 = 137. An OOM killer can send that signal, but so can docker kill, an external watchdog, or an operator.
Confirm the evidence before changing memory.
Capture Docker's state
If a restart policy is looping, disable that policy on the existing container and wait for the next natural failure so that a manual stop does not overwrite its state:
mc_container_id="$(docker compose ps -a -q mc)"
docker update --restart=no "$mc_container_id"
docker wait "$mc_container_id"
docker inspect "$mc_container_id" --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{json .State.Error}} restarts={{.RestartCount}}'
docker compose logs --tail=250 --timestamps mc
Interpret the combination:
exit=137 oom=true: strong evidence that Docker/cgroup memory enforcement killed the containerexit=137 oom=false: investigate anotherSIGKILLsource before adding RAM- JVM
OutOfMemoryErrorin logs with another exit code: the Java heap failed internally, but Docker did not necessarily kill the container
Check container and host limits
Inspect the effective Compose and runtime values:
docker compose config
docker inspect "$mc_container_id" --format 'limit={{.HostConfig.Memory}} swap={{.HostConfig.MemorySwap}}'
docker stats --no-stream "$mc_container_id"
docker info
On a Linux host, look for kernel OOM records around the failure:
journalctl -k --since "-30 min" | grep -Ei 'out of memory|oom-kill|killed process'
With cgroup v2, the counters are also useful while the container is running:
docker compose exec mc sh -c 'cat /sys/fs/cgroup/memory.events 2>/dev/null'
An increasing oom_kill counter confirms kills within that cgroup.
Understand the two memory controls
In itzg/minecraft-server, MEMORY, INIT_MEMORY, and MAX_MEMORY configure the Java heap. Docker's mem_limit caps the entire container, including:
- Java heap
- metaspace and JIT code cache
- native mod-loader and compression allocations
- thread stacks
- direct/network buffers
- image helper and other container processes
The image documentation recommends accounting for an extra 25% beyond heap as a general baseline.
Use a limit with real headroom
This is a sensible starting relationship, not a promise that every modpack fits:
services:
mc:
image: itzg/minecraft-server:java25
restart: unless-stopped
environment:
EULA: "TRUE"
TYPE: PAPER
VERSION: "26.1.2"
MEMORY: 6G
mem_limit: 8g
volumes:
- ./data:/data
The host still needs free memory for Linux, Docker, filesystem cache, backups, and other services. An 8 GB host is not an appropriate home for an 8 GB container limit.
For JVM allocation diagnostics, temporarily add:
environment:
DEBUG_MEMORY: "TRUE"
Remove it when the issue is understood.
Fix based on the evidence
Container limit is too close to heap
Reduce MEMORY or raise mem_limit while preserving host headroom. Recreate the service with docker compose up -d mc; a plain restart does not apply Compose changes.
Host itself is exhausted
Stop competing workloads, lower the Minecraft allocation, or move to a host with more physical RAM. Swap can provide a buffer but is much slower and does not turn an undersized host into a healthy modpack server.
OOMKilled is false
Search deployment logs, automation, docker events, and operator history for an explicit kill:
docker events --since 30m --filter container="$mc_container_id"
Also check stop timeouts. A process that does not exit before the grace period can eventually receive SIGKILL.
Memory keeps growing
Do not keep increasing limits indefinitely. Reproduce without newly added plugins/mods, review pack requirements, and profile the workload. Heap pressure, a native leak, and too many loaded chunks require different fixes.
Validate after the change
docker compose up -d mc
docker compose logs -f mc
docker stats --no-stream "$(docker compose ps -q mc)"
docker inspect "$(docker compose ps -q mc)" --format 'oom={{.State.OOMKilled}} restarts={{.RestartCount}}'
Exercise the workload that previously failed: initial pack loading, world generation, peak players, or backup compression. A successful idle start is not enough.
Avoid dangerous shortcuts
- do not set heap equal to the hard container limit
- do not disable the OOM killer
- do not give the container all host RAM
- do not assume every exit 137 is OOM
- do not hide repeated OOMs behind an unlimited restart policy
Next steps
- If the container still loops for another reason, use the restart-loop guide.
- For a heavy pack, validate its requirements with the CurseForge modpack runbook.
- If memory is stable but ticks are slow, diagnose TPS, MSPT, and server lag.