Signal 9 Fatal Docker & Kubernetes

SIGKILL (Signal 9) in Docker & Kubernetes

Verdict · SIGKILL = signal 9; terminated process exits 137 (128 + 9)

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.

Observed exit137
Signal / meaningSIGKILL
Default actionTerminate (cannot be caught, blocked or ignored)

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.

Compose

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
Kubernetes

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
Runtime

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 limit

Frequently 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.

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