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.
You read it off the pod, not the logs:
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:
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:
kubectl get pod api-7d9f8c6b4-xk2vq \
-o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
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
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).
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.
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.
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.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
The metrics stack you self-host is free software plus a real ops bill. Datadog hands you everything and mails you the invoice. Here's how we pick.
Static keys leak and live forever. Short-lived credentials from STS and Vault expire on their own — here's the token-exchange machinery and the TTL math that make it work.
Explore more articles in this category
Every CI/CD platform claims to be fast and easy. The real differences are in pricing, self-hosting, and where each one falls apart at scale. This is the map.
Woodpecker forked Drone when the license changed. Here's how the two compare for small teams and homelabs that just want simple container-native CI.
Buildkite runs the control plane and lets you own the compute; Actions keeps everything close to your repo. Here's how they actually differ once you scale.
Evergreen posts worth revisiting.