Fixing CrashLoopBackOff Without Guessing
CrashLoopBackOff is not an error, it is a symptom. Here is the order to check things in so you stop guessing and start narrowing.
CrashLoopBackOff tells you a container keeps exiting and Kubernetes keeps restarting it with growing backoff. It does not tell you why. The why is always in one of three places, and checking them in the wrong order is what turns a five minute fix into an afternoon.
Start with the exit code
The exit code is the cheapest signal available and most people skip it. Exit 1 is an application error, 137 is a SIGKILL (usually the OOM killer), 143 is a SIGTERM, and 127 means the command in your entrypoint does not exist.
kubectl get pod my-pod -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
Gives you exitCode, reason and the timestamps in one shot.
Then the previous container's logs
This is the single most common mistake. kubectl logs my-pod shows the current container, which just started and has not failed yet. You want the one that died:
kubectl logs my-pod --previous --tail=100
Then the events
Some failures never reach application logs at all, because the application never started. Failed image pulls, failed readiness probes, and volume mount errors all show up here and nowhere else.
kubectl describe pod my-pod | sed -n '/Events:/,$p'
The usual suspects, in order of frequency
| Symptom | Likely cause | Check |
|---|---|---|
| Exit 137, OOMKilled | Memory limit too low | resources.limits.memory |
| Exit 127 | Entrypoint binary missing | Image contents and command |
| Exit 1 immediately | Missing config or env var | ConfigMap and Secret refs |
| Restarts after ~30s | Readiness or liveness probe | Probe path, port and delay |
| Never starts | Image pull failure | Events and registry credentials |
Working through that table by hand across a cluster is exactly the kind of repetitive triage worth handing off. BashPilot runs the same sequence, reports the conclusion rather than the raw output, and asks before it changes anything. See AI Kubernetes management for how the approval gate works.
Confirm the fix actually worked
Whatever you changed, the way to know it worked is a clean rollout, not the absence of an error in your terminal. Watch the pod through a full restart cycle rather than checking once and moving on:
kubectl rollout status deployment/my-app --timeout=120s
kubectl get pods -l app=my-app -w
A pod that reaches Running and then immediately restarts again a few seconds later has not actually been fixed, it has just moved the failure slightly later. Give it at least two full restart cycles before calling it resolved, and check the fresh logs, not the ones from before your change:
kubectl logs deployment/my-app --tail=50
Stop it coming back
- OOM kills: do not just raise the memory limit and move on. Check what actually changed, a traffic spike, a leak, a new dependency, or the limit was never based on a real measurement in the first place.
- Probe failures: if the app genuinely needs more than the default time to become ready, set
initialDelaySecondsto match reality instead of shortening the probe's patience. - Missing config on deploy: a ConfigMap or Secret that has not been created yet is a sequencing problem, not a one-off. Add it to whatever applies your manifests, so it can never again be applied out of order.
- Image pull failures: confirm the registry credentials are attached to the correct namespace, not just present somewhere in the cluster.
See how BashPilot handles this
The haproxy pod had no config file. The image expects one at /usr/local/etc/haproxy/haproxy.cfg and exits immediately without it.
- Created a ConfigMap with a working frontend and backend, ports 80 and 443 routed to the app backend.
- Updated the deployment to mount it at
/usr/local/etc/haproxy/haproxy.cfg. - New pod is Running, 1/1 ready, logs show
Loading success.
kubectl rollout status deployment/haproxy
Try this on one of your own servers.
Start free trial