Exit Code 137 (SIGKILL) in Docker & Kubernetes
Exit code 137 equals 128 + 9: SIGKILL, which cannot be caught, blocked or ignored. Two production causes dominate: the cgroup or node OOM killer selected the process, or the platform killed it after a failed liveness probe, a docker kill or an eviction. PID 1 got no chance to clean up.
Proof commands: confirm the exit status and the killer
Run these before changing anything. The first two identify what the runtime recorded; the third finds a kernel-level kill on the host. Replace the placeholder container ID, pod name or host path with your own.
Docker inspect
docker inspect <container_id> --format '{{.State.Status}} | OOMKilled: {{.State.OOMKilled}} | ExitCode: {{.State.ExitCode}}'Kubernetes state
kubectl get pod <pod_name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\tExit:"}{.lastState.terminated.exitCode}{"\n"}{end}'Kernel kill log
sudo dmesg -T | grep -E -i 'oom[_-]killer|kill process'If OOMKilled is true, the cgroup limit is the cause and the application log will usually end mid-operation with no exception. If OOMKilled is false, run kubectl describe pod and read the Liveness probe failed and Killing events. Also check the cgroup memory.events file: an oom_kill counter above zero confirms a cgroup-level kill even when the status reason is vague.
Three production root causes
128 + 9: SIGKILL cannot be caught. Check State.OOMKilled and the Kubernetes lastState reason first. OOMKilled true means the cgroup memory limit was exceeded; reason Error with a liveness probe event means the kubelet killed a container that ignored the stop signal (SIGTERM by default). No handler can prevent SIGKILL.
1. The container exceeded its cgroup memory limit
Docker --memory, Compose deploy.resources.limits.memory and Kubernetes resources.limits.memory all set a cgroup limit. The kernel selects a process in that cgroup and sends SIGKILL; the container records OOMKilled true and exit 137. A runtime whose heap is sized above the limit is a classic case. Older JVMs and runtimes without cgroup awareness size themselves from host memory; container-aware JVMs (Java 10+, 8u191+) are still killed when -Xmx or MaxRAMPercentage exceeds limits.memory. Node's default old-space size does not follow the cgroup limit, so set --max-old-space-size explicitly.
2. A liveness probe failed and the kubelet restarted the container
The kubelet has the runtime send the image's stop signal (SIGTERM by default), waits terminationGracePeriodSeconds and then sends SIGKILL. If the application ignores that signal, the last state is Error with exit 137 even though the root cause is the probe. Read kubectl describe pod: Liveness probe failed, Killing and the probe's timeout and failureThreshold tell you whether the probe was too aggressive.
3. Node memory pressure, eviction or a manual kill
When the node itself runs out of memory the kernel OOM killer can choose any large process; dmesg shows the kill and Docker or Kubernetes may report 137 without OOMKilled on the container. kubectl delete --force, docker kill and a host reboot during shutdown also produce 137 because they do not allow the process to exit.
Copy-ready fixes for Docker Compose and Kubernetes
The two manifests set explicit memory limits and a termination grace period. Use them as a baseline, then adjust the values to the measured working set of the service.
docker-compose.yml
The memory limit is the line the kernel enforces; the reservation is scheduling guidance. Set the limit from measured peak RSS with headroom, and set the runtime's own heap ceiling below the limit. On the host, docker inspect .State.OOMKilled confirms which cgroup was killed.
services:
app:
image: ghcr.io/acme/app:1.4.2
restart: on-failure:3
stop_grace_period: 30s
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
deployment.yaml
requests are used for scheduling and the OOM score; limits are the hard cgroup boundary. Set the heap or runtime memory ceiling below limits.memory. Keep the liveness probe slower than a normal garbage-collection pause: an aggressive probe restarts a healthy but busy process and the restart is recorded as 137.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 2
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
image: ghcr.io/acme/app:1.4.2
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
Keep the runtime heap below the cgroup limit
The container limit is invisible to runtimes that read host memory unless they are configured to respect cgroups.
# JVM: use the cgroup limit instead of host RAM
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0"
# Node.js: cap the old space below the container limit
NODE_OPTIONS="--max-old-space-size=384" # limit 512MiFrequently asked questions
How do I confirm an OOM kill?
Run docker inspect <id> --format '{{.State.OOMKilled}}' or read the Kubernetes lastState.terminated.reason. OOMKilled true is decisive. On the node, dmesg shows the oom-killer invocation and the victim. A cgroup memory.events file with a non-zero oom_kill counter also confirms the kill.
Can I catch SIGKILL or write a handler for exit 137?
No. SIGKILL cannot be caught, blocked or ignored, and it does not run deferred cleanup. The correct response is prevention: keep memory below the cgroup limit, size runtime heaps correctly, and make sure liveness probes do not kill a busy process. Use SIGTERM handling for graceful shutdown instead.
Why did my pod exit 137 when it did not run out of memory?
Check the liveness probe events. The kubelet sends the image's stop signal (SIGTERM by default) and, if the process does not exit within the grace period, SIGKILL. That produces 137 with OOMKilled false. A manual docker kill or force deletion has the same effect. The exit status only says SIGKILL, not who sent it.
Related exit codes, signals and network references
Follow the status you actually observed, then check the related port or calculator when the failure involves networking, exposure or firewall rules.
Reference basis: POSIX signal semantics (128 + n), Docker container exit status conventions, and Kubernetes container termination behaviour. Verify the exact behaviour against your runtime version before relying on it in an incident.
Last reviewed 2026-11-06 · All exit codes · Developer tools · Privacy