Exit code 130 Warning Docker & Kubernetes

Exit Code 130 (SIGINT) in Docker & Kubernetes

Verdict · 130 = 128 + 2 (SIGINT)

Exit code 130 equals 128 + 2, so the process terminated on SIGINT. SIGINT is what Ctrl+C sends to the foreground process group. Unless the application catches it to shut down gracefully, the default terminate action applies and the shell, Docker or kubectl reports 130.

Observed exit130
Signal / meaningSIGINT
Default actionWarning

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}} | ExitCode: {{.State.ExitCode}} | OOMKilled: {{.State.OOMKilled}}'

Kubernetes state

kubectl get pod <pod_name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\tExit:"}{.lastState.terminated.exitCode}{"\n"}{end}'

Signal mapping

kill -l 130; docker events --since 15m --filter event=kill --format '{{.Actor.Attributes.name}} {{.Actor.Attributes.signal}}'

SIGINT is sent by a terminal, not by the kernel OOM path. OOMKilled true means 137; 130 with OOMKilled false means an interrupt. In a Kubernetes pod, the kubelet sends the image's stop signal (SIGTERM by default), so exit 130 usually comes from a human running kill -INT or pressing Ctrl+C inside kubectl exec.

Three production root causes

128 + 2: the process was interrupted, usually by Ctrl+C. In interactive Docker sessions the interrupt is proxied to PID 1; Kubernetes stops pods with the image's stop signal (SIGTERM by default), not SIGINT. Trap SIGINT when partial work must be cleaned up.

1. Ctrl+C in docker run or docker attach

An interactive TTY proxies SIGINT from your terminal to PID 1 when --sig-proxy is enabled, which is the default. The container stops with 130. Use docker run -d for services, and keep interactive runs for debugging only. docker attach has its own detach key sequence so a Ctrl+C detaches or forwards depending on flags.

2. Ctrl+C inside kubectl exec

An interactive exec session sends the terminal interrupt to the remote command, not to the pod's main process unless you target PID 1. The command you started can exit 130 while the container keeps running. Read kubectl exec's own return status separately from the pod status.

3. A supervisor is configured to send SIGINT

Compose stop_signal: SIGINT and systemd StopSignal=SIGINT make a normal stop look like an interrupt. If the application has no SIGINT handler, it dies immediately instead of draining. Either add a handler for both INT and TERM, or stop overriding the default SIGTERM stop signal.

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

stop_signal: SIGINT sends SIGINT on docker compose stop. Only set it if the application handles SIGINT and drains; otherwise keep the default SIGTERM and handle that. The stop_grace_period is the time between the signal and SIGKILL.

services:
  app:
    image: ghcr.io/acme/app:1.4.2
    restart: on-failure:3
    stop_grace_period: 20s
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
    stop_signal: SIGINT
Kubernetes

deployment.yaml

Kubernetes has no stable pod-level field for choosing the stop signal; the kubelet asks the runtime to send the image's STOPSIGNAL, SIGTERM by default, then SIGKILL. An application that only handles SIGINT will ignore a SIGTERM stop and be killed with SIGKILL, producing 137 after the grace period.

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"
Entrypoint

Drain on SIGINT and SIGTERM

Handle both signals so an interactive stop and a Kubernetes rollout share one path.

#!/bin/sh
set -eu
shutdown() {
  echo "interrupt received: draining"
  kill -TERM "$child" 2>/dev/null || true
  wait "$child" 2>/dev/null || true
  exit 0
}
trap shutdown INT TERM
/usr/local/bin/app &
child=$!
wait "$child"

Frequently asked questions

Why is Ctrl+C exit code 130?

Ctrl+C sends SIGINT, signal 2, to the foreground process group. The shell reports a signal termination as 128 + 2 = 130. A program that catches SIGINT can return 0 instead, but the default action terminates it.

Does Kubernetes send SIGINT when a pod is deleted?

No. Pod termination sends the image's stop signal (SIGTERM by default), waits terminationGracePeriodSeconds and then sends SIGKILL. SIGINT only reaches the container if a person or a process inside sends it, or the image sets STOPSIGNAL SIGINT. Handle the configured stop signal for graceful shutdown in Kubernetes.

How do I stop a container without an interrupt status?

Let the process handle SIGINT and exit 0, or use SIGTERM with a proper handler and give it enough grace time. If you only want to detach from logs, use the docker attach detach sequence instead of Ctrl+C, or run the container detached.

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