Skip to main content
Kubernetes Container Exit Codes Decoded

Kubernetes Container Exit Codes Decoded

KU
• 7 min read•35 views

Every crash loop leaves a number behind. Learn to read exit codes straight from kubectl describe and know the fix before you open the logs.

A pod dies. You get paged. Before you go spelunking through application logs, there is one number that tells you most of the story: the exit code. It is the single integer a container hands back to the kernel on its way out, and Kubernetes faithfully records it. Learn to read it and you can triage half your incidents in about ten seconds.

We spend a lot of time on this at work because the exit code is honest. Logs can be swallowed by a buffer that never flushed. Metrics can lag. The exit code is written the instant the process ends, and it is sitting right there in the pod status waiting for you.

Where the number lives

You read it off the pod, not the logs:

bash.bash
kubectl describe pod api-7d9f8c6b4-xk2vq

Scroll to the container block and look at Last State. Here is a real one from a service that kept dying on us last quarter:

code
Containers:
  api:
    Container ID:   containerd://8f3e...
    Image:          registry.internal/api:1.42.0
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Wed, 08 Jul 2026 03:14:02 +0000
      Finished:     Wed, 08 Jul 2026 03:14:19 +0000
    Ready:          False
    Restart Count:  6

State: Waiting with Reason: CrashLoopBackOff tells you the container is stuck restarting. Last State: Terminated tells you why the last attempt died. The Exit Code and Reason are the payload. If you want just the number without the wall of text:

bash.bash
kubectl get pod api-7d9f8c6b4-xk2vq \
  -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

The reference table

code
Exit  Meaning                         First thing to check
----  ------------------------------  --------------------------------
0     Clean exit                      Job done, or a container that
                                      shouldn't have stopped
1     General application error       App logs, unhandled exception
125   Container runtime failed        Image, runtime, bad docker/CRI
126   Not executable / permission     File mode, entrypoint bit
127   Command not found               Typo in command, missing binary
128   Invalid exit argument           Rare, app called exit() wrong
130   128 + 2  SIGINT                 Ctrl-C, interrupt
137   128 + 9  SIGKILL                OOMKilled, or a failed kill
139   128 + 11 SIGSEGV                Segfault, native memory bug
143   128 + 15 SIGTERM               Shutdown, graceful or not

How signals become numbers

The pattern behind everything from 128 up is a simple bit of Unix convention: when a process is killed by a signal, its exit code is 128 + signal number. So SIGKILL is signal 9, which gives you 137. SIGTERM is 15, which gives you 143. SIGSEGV is 11, giving 139. Memorize 128 + and you can decode any of them by subtracting 128 and looking up the signal.

This is why 137 and 143 are the two you will see most. Kubernetes runs on signals. When it wants a container gone politely, it sends SIGTERM (143). When politeness runs out, it sends SIGKILL (137).

The codes worth knowing cold

0 — clean exit. The process finished with no error. For a Job or an init container, this is what you want. For a long-running service, a 0 is suspicious: something told your main process to stop and it obliged. Check whether a script is exiting early or a supervisor inside the container is quitting.

1 — general application error. The catch-all. Your code threw and nothing caught it, a config file was missing, a required env var was unset. The exit code gives you nothing more here; this is the one case where you go straight to the logs.

125 — the container runtime itself failed. The image never really started. Wrong architecture, a corrupt layer, an entrypoint the runtime could not launch. This is not your app failing; it is containerd or the image being wrong. Check the image reference and the node's runtime before you blame your code.

126 — permission or not executable. The binary is there but the kernel refused to run it. Usually the entrypoint script lost its executable bit somewhere in the build. A chmod +x in the Dockerfile fixes most of these.

127 — command not found. The single most common self-inflicted wound. Your entrypoint points at a binary that is not on the path, or you have a typo, or you built a slim base image that dropped bash. We have all shipped a container that expects /bin/bash onto Alpine, which only has sh.

137 — SIGKILL, almost always OOMKilled. The container blew past its memory limit and the kernel's OOM killer put it down hard. The giveaway is Reason: OOMKilled right above the exit code. Raise the limit or fix the leak. We wrote up the full triage in OOMKilled, because it earns its own post. The other, rarer 137 is a SIGKILL that landed because a container ignored SIGTERM and Kubernetes escalated after the grace period.

139 — segfault. SIGSEGV, signal 11. The process touched memory it had no business touching. In pure Go or Java this is rare; when you see it, suspect a native library, a CGO boundary, or a corrupt binary. This is a real bug, not a config problem, and it needs a debugger or a core dump.

143 — SIGTERM. The most misread code on this list. 143 means the container received SIGTERM. That is exactly what Kubernetes sends during every normal shutdown, scale-down, and rolling deploy. So 143 on its own is often completely fine. It only means trouble when it pairs with a CrashLoopBackOff, which tells you the app is being asked to stop and treating it as a crash.

130 — SIGINT. Signal 2, the Ctrl-C code. You rarely see this from Kubernetes itself; it shows up when someone attaches to a container and interrupts it, or when a shell wrapper forwards an interrupt.

Graceful termination, and why 143 confuses people

When Kubernetes removes a pod, it does not just yank it. It sends SIGTERM to the main process, then waits terminationGracePeriodSeconds (default 30) for the process to shut down on its own. Only if the timer runs out does it send SIGKILL.

So a well-behaved app catches SIGTERM, drains connections, flushes state, and exits 143 within the grace window. Good. A badly behaved app ignores SIGTERM, sits there until the 30 seconds elapse, and then gets a 137. That slow, ugly shutdown shows up as latency during every deploy. If your rollouts feel sticky, check whether your process actually handles SIGTERM.

The other trap is the liveness probe. A failing liveness probe does not exit gracefully in your app's mind, but Kubernetes kills the container the same way, and you will see the restart with a signal-based code. If a container keeps dying with 143 or 137 and you never asked it to stop, look at your probe thresholds before you look at your code. We have watched an under-tuned liveness probe murder a perfectly healthy service that was just slow to warm up. For the wider playbook, our Kubernetes troubleshooting guide covers the probe traps in depth.

The call we'd make

Read the exit code first, every time. Treat 137 as memory until proven otherwise, treat 127 and 126 as build problems, and never panic at a lone 143. The number is not the whole diagnosis, but it points you at the right half of the haystack before you have read a single log line. That is the fastest triage tool Kubernetes gives you for free, so use it.

Explore topics:KubernetesDevOps
React
Share this post
KU

Kiril Urbonas

AI Engineer

553 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.