OOMKilled (exit code 137): fixing out-of-memory pods
By Hackademy. Updated . Output from a real Kubernetes v1.35 cluster.
The short answer
OOMKilled means the Linux kernel's out-of-memory killer stopped a container, almost always because it tried to use more memory than its resources.limits.memory. The exit code is 137: 128 plus 9, the number of SIGKILL, so the process had no chance to clean up or log anything. Kubernetes then restarts the container, and repeated kills turn into CrashLoopBackOff. The usual causes are a limit set below what the application really needs, a memory leak, a sudden spike in load or data size, and a runtime such as the JVM sizing its heap without regard to the container's limit. To diagnose it, run kubectl get pods to see the restarts, kubectl describe pod POD to read Last State Terminated, Reason OOMKilled, Exit Code 137, and kubectl top pods to measure real usage. Fix it by setting the limit from measured usage with headroom, for example with kubectl set resources, or by bringing the application's memory down.
Confirm the container was OOMKilled
This pod runs a busybox command that allocates about 300MiB, with a memory limit of 64Mi. kubectl get pods shows the reason straight in STATUS, and RESTARTS climbing:
$ kubectl get pod hog
NAME READY STATUS RESTARTS AGE
hog 0/1 OOMKilled 2 (23s ago) 25sSTATUS only shows the latest state, and between restarts it soon changes to CrashLoopBackOff. kubectl describe pod keeps the evidence in the container's Last State, beside the limit it broke:
$ kubectl describe pod hog
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Sun, 27 Sep 2026 20:14:41 +0100
Finished: Sun, 27 Sep 2026 20:14:41 +0100
Ready: False
Restart Count: 2
Limits:
memory: 64Mi
Requests:
memory: 64MiExit code 137 on its own only says the process received SIGKILL. Reason OOMKilled is what ties it to memory. A 137 without that reason has another source, such as a container killed after its termination grace period ran out.
Know what the limit and the request do
A container's memory has two settings, and they do different jobs:
- The request is what the scheduler reserves. A pod is placed only on a node with that much unreserved memory, and it is what an autoscaler measures utilisation against.
- The limit is a hard ceiling enforced by the kernel. Go over it and the container is killed. Memory, unlike CPU, cannot be throttled, so there is no slowing down first.
$ kubectl explain pod.spec.containers.resources
limits <map[string]Quantity>
Limits describes the maximum amount of compute resources allowed. More info:
https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
requests <map[string]Quantity>
Requests describes the minimum amount of compute resources required. If
Requests is omitted for a container, it defaults to Limits if that is
explicitly specified, otherwise to an implementation-defined value. Requests
cannot exceed Limits. More info:
https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/A pod can also be killed when the node itself runs short of memory, even under its own limit. The kubelet then evicts pods, starting with those using the most memory beyond their requests, and the pod's status says Evicted rather than OOMKilled. Setting requests close to real usage protects a pod from that.
Measure real usage, then set the limit
Guessing a bigger number works until the next spike. Measure first. kubectl top pods reads current usage from the metrics server, so watch it under realistic load, not just at idle:
$ kubectl top pods
NAME CPU(cores) MEMORY(bytes)
api-5657664566-2g8dz 1m 9Mi
api-5657664566-v2jh8 1m 9MiThen set the limit above the peak you saw, with headroom, and the request near typical usage. kubectl set resources changes the Deployment's pod template, which rolls out new pods with the new values:
$ kubectl set resources deployment api --limits=memory=256Mi
deployment.apps/api resource requirements updated$ kubectl get pods -l app=api
NAME READY STATUS RESTARTS AGE
api-5657664566-2g8dz 1/1 Running 0 15s
api-5657664566-v2jh8 1/1 Running 0 14sCheck RESTARTS over the following hours, not just the first minute. If usage keeps growing until the kill whatever the limit, the problem is a leak, and a bigger limit only delays it.
Make the application fit its limit
- Size language runtimes from the container: modern JVMs read the cgroup limit, and
-XX:MaxRAMPercentagesets the heap as a share of it. Leave room for memory outside the heap. - Stream large files and query results instead of loading them whole.
- Bound caches and in-memory queues by size, not only by time.
- Split memory-hungry batch work into a Job with its own, larger limit rather than raising the limit for every replica of a web service.
To practise the whole loop, the missions below give you an API whose memory limit is too tight and check that its pods stay up afterwards. They run in the browser with nothing to install.
Practise it on a simulated cluster
These missions in the Kubernetes Simulator break a cluster in this way and leave you to fix it. Free in your browser, nothing to install.
- Out of memoryREST API, intermediate, about 10 min
- CrashLoopBackOffREST API, intermediate, about 10 min
- Launch-day trafficREST API, advanced, about 12 min
Questions
- Why is the exit code 137?
When a process is ended by a signal, its exit code is 128 plus the signal number. SIGKILL is signal 9, so a container killed by the out-of-memory killer exits with 137. SIGKILL cannot be caught, which is why the application usually logs nothing about it.
- Should I just remove the memory limit?
Without a limit a container can use as much memory as the node has, and a leak in one pod can then push the node into memory pressure and get other pods evicted. Keep a limit, set from measured usage, and treat repeated OOM kills as a signal to investigate.
- Does OOMKilled always mean the container's own limit was hit?
Nearly always. If the node runs out of memory before the kubelet can evict pods, the kernel's out-of-memory killer can also pick a process in a container that was under its own limit.
kubectl describe nodeand the node's memory pressure condition help tell the two apart.