SIGKILL (Signal 9) in Docker & Kubernetes
SIGKILL is signal 9 and the only signal a process cannot catch, block or ignore. The kernel terminates it immediately and the parent observes exit 137 = 128 + 9. In production it usually comes from the cgroup OOM killer, a failed liveness probe, an eviction or an explicit kill -9.
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'There is no handler to inspect because SIGKILL cannot be caught. Proof is the sender: cgroup OOMKilled, kubelet probe events, docker events, or dmesg. The application log ending without an exception is expected, not evidence that the app has no bug.
Three production root causes
Default action: terminate; it cannot be caught, blocked or ignored. The process gets no cleanup and no chance to drain. Find the sender: OOMKilled true means memory, a probe event means the kubelet, and dmesg or docker events show manual kills.
1. The cgroup memory limit was exceeded
The kernel kills a process inside the cgroup, and Docker or Kubernetes records OOMKilled true with exit 137. Runtime heaps that size themselves from host memory can exceed the container limit even when the node has free RAM. Keep the heap ceiling below the limit and test with the production limit applied.
2. A liveness probe failed and the grace period expired
The kubelet sends the image's stop signal (SIGTERM by default), waits terminationGracePeriodSeconds, then SIGKILL. If the process does not exit, the last status is 137 with OOMKilled false. Read the probe events and the app's shutdown path; the fix is usually a handler or a less aggressive probe, not more memory.
3. A human, script or node eviction sent SIGKILL
kill -9, docker kill, and node memory-pressure eviction bypass cleanup. dmesg and docker events show the kill. A force-deleted pod is removed from the API but the kubelet still performs its own termination sequence, so verify the node rather than assuming the API deletion killed the process.
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
You cannot trap SIGKILL in the container. The only controls are the memory limit and the timeout before Docker escalates: docker stop -t gives SIGTERM time, then sends SIGKILL. Keep the application's shutdown faster than that timeout.
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
SIGKILL is the kubelet's last resort after the grace period. Make the application exit on SIGTERM, keep the probe tolerant of normal pauses, and set the memory limit above measured peak usage. A restart count with OOMKilled true is a sizing problem; with OOMKilled false it is usually a shutdown or probe problem.
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 heap and cgroup headroom aligned
SIGKILL cannot be handled, so the only defense is staying below the limit.
# Inspect cgroup v2 OOM counters on the host
cat /sys/fs/cgroup/system.slice/docker-<id>.scope/memory.events
# oom_kill 3
# JVM and Node: cap the heap below the container limit
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0"
NODE_OPTIONS="--max-old-space-size=384" # for a 512Mi limitFrequently asked questions
Can SIGKILL be caught or ignored?
No. SIGKILL and SIGSTOP cannot be caught, blocked or ignored. The kernel terminates the process immediately, deferred cleanup does not run, and the parent sees exit status 137. Design for graceful shutdown using SIGTERM, not SIGKILL.
Does exit code 137 always mean OOM?
No. It always means SIGKILL, but the sender can be the cgroup OOM killer, the kubelet after a failed liveness probe and expired grace period, an eviction, docker kill or a manual kill -9. Check OOMKilled and the probe events to identify the sender.
What is the difference between kill -9 and SIGTERM?
SIGTERM (15) is a request the process can catch and handle for a graceful shutdown; if unhandled, the default action terminates it with exit 143. SIGKILL (9) cannot be handled and terminates immediately with exit 137. Kubernetes sends the image's stop signal (SIGTERM by default) first, then SIGKILL after the grace period.
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