Exit code 143 Warning Docker & Kubernetes

Exit Code 143 (SIGTERM) in Docker & Kubernetes

Verdict · 143 = 128 + 15 (SIGTERM)

Exit code 143 equals 128 + 15: SIGTERM terminated the process. By default, docker stop, docker compose down, Kubernetes pod deletion and systemd all use SIGTERM as the stop request. It is a problem only when the workload had in-flight work and no handler: the process died instead of draining within the grace period.

Observed exit143
Signal / meaningSIGTERM
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}} | 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 20

143 is a clean signal termination, so OOMKilled is false. If the app logs a SIGTERM line and then a database rollback, the shutdown began correctly and the exit status is expected. If the logs stop mid-request with no shutdown line, PID 1 never received the signal or the handler was absent.

Three production root causes

128 + 15: the standard graceful stop request. When SIGTERM is the configured stop signal, Docker waits its stop timeout (10s by default) and Kubernetes waits terminationGracePeriodSeconds (30s by default), then sends SIGKILL. Handle SIGTERM, stop accepting work, drain and exit 0 before the deadline.

1. PID 1 is a shell that does not forward signals

The shell form CMD app runs the binary as a child, but the kernel sends SIGTERM to PID 1, the shell. A non-interactive dash or BusyBox sh may not forward it, so the app keeps running until the grace period expires and SIGKILL arrives. Use exec-form ENTRYPOINT or exec "$@" so the application becomes PID 1.

2. The application ignores SIGTERM or exceeds its grace budget

A handler can be missing, or it can wait on a long migration, a slow database commit, non-cancellable HTTP handlers or a sleep that does not respond to context cancellation. Docker's default is 10 seconds; Kubernetes defaults to 30. After that the runtime sends SIGKILL, so the last status may be 137 rather than 143.

3. A preStop hook consumes the grace period

The preStop hook and the app's own drain share terminationGracePeriodSeconds. A 20-second preStop sleep plus a 20-second drain cannot finish in a 30-second grace window; kubelet sends SIGKILL at the deadline. Budget the hook and the drain together and raise the grace period deliberately.

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

Docker's default stop timeout is 10 seconds. stop_grace_period raises it for compose down and stop. Give the application a shorter internal drain target than the platform budget so it can exit 0 instead of being killed.

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
Kubernetes

deployment.yaml

terminationGracePeriodSeconds is the total budget: preStop plus SIGTERM handling plus process exit. Keep the preStop hook short, stop accepting new requests immediately, and finish in-flight work before the deadline. A server that ignores the stop signal (SIGTERM by default) will be killed with SIGKILL and recorded as 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"]
Entrypoint

Become PID 1 and forward SIGTERM

The exec form removes the shell from the signal path.

# Shell form: the shell is PID 1 and may not forward SIGTERM
# CMD ./app

# Exec form: the app is PID 1 and receives SIGTERM directly
CMD ["./app"]

# If an entrypoint wrapper is required, exec the child:
# exec /usr/local/bin/app "$@"
Application

Stop accepting, drain, then exit 0

A handler that exits 0 turns 143 into a successful planned shutdown.

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

Frequently asked questions

Is exit code 143 a crash?

Usually not. It means SIGTERM terminated the process, which is the default stop request for Docker, Kubernetes and systemd. It becomes a problem when the app had in-flight work and no handler. A crash caused by memory or a native fault would be 137 or 139.

Why did my Kubernetes pod exit 137 after receiving SIGTERM?

The container ignored or outlasted SIGTERM. The kubelet waited terminationGracePeriodSeconds, then sent SIGKILL, so the last state is 137. Check whether PID 1 is a shell, whether preStop plus drain exceeds the grace period, and whether the handler actually exits.

How long does Kubernetes wait before SIGKILL?

terminationGracePeriodSeconds, 30 seconds by default. The preStop hook and the application's shutdown share that single budget. Set the value above your measured worst-case drain plus hook time, and keep the application's own deadline shorter so it can exit cleanly.

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