SIGTERM (Signal 15) in Docker & Kubernetes
SIGTERM is signal 15 and the standard graceful termination request. Its default action is terminate, producing exit 143 = 128 + 15 when unhandled. Docker, Kubernetes and systemd use it as the default stop signal before escalating; the process should stop accepting work and exit before the grace period ends.
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}} | StopSignal: {{.Config.StopSignal}} | StopTimeout: {{.HostConfig.StopTimeout}}'Kubernetes state
kubectl get pod <pod_name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\tExit:"}{.lastState.terminated.exitCode}{"\n"}{end}'Grace budget
kubectl get pod <pod_name> -o jsonpath='{.spec.terminationGracePeriodSeconds}{"\n"}'; sudo dmesg -T | tail -n 20An exit of 143 with a shutdown line in the log is a successful planned stop. If the log ends without a SIGTERM line, PID 1 did not receive or handle the signal. If the status is 137 instead, the process ignored SIGTERM and the platform had to escalate.
Three production root causes
Default action: terminate. It is catchable, so drain connections, finish in-flight work and exit 0. Docker waits 10s by default and Kubernetes 30s, then sends SIGKILL. A shell PID 1 that does not forward signals turns every shutdown into a 137.
1. PID 1 is a shell wrapper that does not forward signals
Shell-form CMD or ENTRYPOINT starts the binary as a child while the shell remains PID 1. A non-interactive shell may not forward SIGTERM, so the app survives until SIGKILL. Use exec-form ENTRYPOINT, exec "$@", or a tiny init such as tini or dumb-init.
2. The handler is missing or the drain exceeds the budget
Long database migrations, non-cancellable HTTP handlers, sleeps and blocking queue consumers can outlive the grace period. Docker defaults to 10 seconds and Kubernetes to 30. Stop accepting new work immediately, bound the drain, and exit before the deadline.
3. The preStop hook and drain share one budget
terminationGracePeriodSeconds covers the preStop hook and the application's shutdown together. A 20-second preStop plus a 20-second drain cannot fit in 30 seconds. Budget the two phases explicitly and raise the grace period only after measuring the worst case.
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_grace_period controls the time between SIGTERM and SIGKILL for docker compose stop and down. Keep the application's own shutdown deadline shorter than this value so it can exit cleanly.
services:
app:
image: ghcr.io/acme/app:1.4.2
restart: on-failure:3
stop_grace_period: 60s
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
stop_signal: SIGTERM
deployment.yaml
terminationGracePeriodSeconds is shared by the preStop hook, the SIGTERM handler and process exit. Make the application exit 0 within its share of the budget; otherwise kubelet escalates to SIGKILL and the container reports 137.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 2
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
image: ghcr.io/acme/app:1.4.2
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
Make the application PID 1
Exec form removes the shell that would otherwise swallow SIGTERM.
# Avoid: CMD ./app (a shell becomes PID 1)
CMD ["./app"]
# With a wrapper, replace the shell instead of forking:
# exec /usr/local/bin/app "$@"Frequently asked questions
What is a graceful shutdown with SIGTERM?
The process catches SIGTERM, stops accepting new connections or jobs, finishes or cancels in-flight work, releases resources and exits 0 before the platform's grace period expires. If it does not, the platform sends SIGKILL and the observed status is 137.
Why does my container exit 143 on every deployment?
A deployment stops the old pod with SIGTERM, so 143 is the expected outcome if the process does not catch the signal. It is not necessarily an error. If you need a clean 0, install a handler that drains and exits 0; if the app needs more time, raise terminationGracePeriodSeconds and keep the preStop hook short.
What happens if the application ignores SIGTERM?
The platform waits its configured timeout, then sends SIGKILL. Docker uses the stop timeout (10s by default) and Kubernetes uses terminationGracePeriodSeconds (30s by default). The final exit status is 137, and in-flight requests or transactions are lost.
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