SIGINT (Signal 2) in Docker & Kubernetes
SIGINT is signal 2 and the default action is terminate; a process killed by it exits 130 = 128 + 2. The terminal sends it on Ctrl+C. Containers can receive it in interactive sessions or when stop_signal is set to SIGINT, but Kubernetes has no stable pod-level stop-signal field and uses the image's STOPSIGNAL, SIGTERM by default.
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 2; docker events --since 15m --filter event=kill --format '{{.Actor.Attributes.name}} {{.Actor.Attributes.signal}}'SIGINT is delivered by a terminal or an explicit kill; it is not part of the OOM path. OOMKilled false and exit 130 means someone interrupted the process. In Kubernetes, an exit 130 from the main container is unusual because pod deletion sends the image's stop signal, SIGTERM by default; look for a human or in-container command.
Three production root causes
Default action: terminate. It is the Ctrl+C user interrupt and is catchable, so a handler can cancel work and return 0. Docker proxies it to PID 1 in interactive sessions; Kubernetes sends the image's stop signal (SIGTERM by default), so graceful logic must handle SIGTERM too.
1. Ctrl+C during an interactive Docker session
The CLI proxies the terminal interrupt to PID 1 by default. The container stops with 130 and the shell reports it. Use detached mode for services and reserve interactive attach for debugging. With docker attach, the detach sequence avoids sending a signal.
2. An explicit SIGINT stop signal
Compose stop_signal: SIGINT and systemd StopSignal=SIGINT change the stop request. If the app has no handler, every planned stop looks like an interrupt. If it has a handler, verify that the handler drains and exits 0 rather than re-raising the default disposition.
3. SIGINT inside kubectl exec
The interrupt goes to the command started by kubectl exec, not to the container's PID 1. The exec returns 130 while the pod remains running. Do not use that status to conclude the container crashed; read the pod's own lastState separately.
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
stop_signal: SIGINT is valid but non-standard. Keep SIGTERM as the stop signal unless the application specifically documents SIGINT. If you use SIGINT, handle both SIGINT and SIGTERM so Kubernetes and Compose follow the same shutdown path.
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
deployment.yaml
Kubernetes has no stable pod-level field for choosing SIGINT; the kubelet asks the runtime to send the image's STOPSIGNAL, SIGTERM by default, then SIGKILL. An app that only handles SIGINT will ignore a SIGTERM stop and be killed with SIGKILL, producing 137. Trap both signals to share one shutdown routine; STOPSIGNAL SIGINT in the image is possible but non-standard.
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"
Handle SIGINT and exit cleanly
Returning 0 from an intentional interrupt prevents false crash alerts.
#!/bin/sh
set -eu
shutdown() {
echo "SIGINT: cancelling work"
kill -TERM "$child" 2>/dev/null || true
wait "$child" 2>/dev/null || true
exit 0
}
trap shutdown INT
/usr/local/bin/app &
child=$!
wait "$child"Frequently asked questions
Why is exit code 130 used for Ctrl+C?
The shell reports a process terminated by signal n as 128 + n. Ctrl+C sends SIGINT, signal 2, so 128 + 2 = 130. If the program catches SIGINT and returns normally, the observed status can be 0 instead.
Can I ignore SIGINT in a container?
Technically yes, but then Ctrl+C cannot stop the process and you must use docker kill or another signal. Ignoring SIGINT in Kubernetes is especially misleading because the default stop signal is SIGTERM (SIGINT is only sent when the image sets STOPSIGNAL SIGINT); handle SIGTERM instead.
Does SIGINT always mean a human pressed Ctrl+C?
No. The signal can come from kill -INT, a supervisor configured with a SIGINT stop signal, or a terminal emulator closing. Check who owns the process and the surrounding automation before assuming a person interrupted 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