CrashLoopBackOff is not an error: it is the state where Kubernetes tells you it gave up retrying so often. The container started, died, and the kubelet brought it back until it decided to wait longer between attempts.
The kubelet starts your container. The container exits — with an error or without one. The kubelet restarts it. If it exits again, it waits before the next attempt, and that wait doubles: 10 seconds, 20, 40, 80, up to a ceiling of five minutes.
That "wait longer each time" is the BackOff in the name. When you see it in `kubectl get pods`, the pod may have gone minutes without attempting anything, so the last log line you see can be from a while ago.
One detail that confuses people: a container that exits successfully also lands in this state. If your command does its job and exits with code 0, Kubernetes restarts it anyway, because a Deployment expects a process that does not finish. That is not an application failure, it is a container designed for the wrong object.
First the events and the previous state. `kubectl describe pod <name>` shows the Events section at the bottom and, higher up, `Last State` with the exit code and reason. That alone is often enough.
Then, and this is the part most people skip: `kubectl logs <name> --previous`. Without `--previous` you are reading the current container, which probably has not started yet or is already dead. With `--previous` you read the logs of the attempt that failed, which is where the cause is.
If the pod has several containers, add `-c <container>`. And if you need the full sequence, `kubectl get events --sort-by=.lastTimestamp` in the namespace orders everything that happened.
**137** means the process received SIGKILL. In the vast majority of cases it is the kernel killing it over memory: the container went past its limit. `kubectl describe` confirms it with `Reason: OOMKilled`. That is a different problem and has its own page.
**143** is SIGTERM: something asked it to shut down cleanly. Usually a liveness probe that declared the container unhealthy and triggered the restart, not an application failure as such.
**1** or **2** is the application failing on its own: a missing environment variable, a secret that is not mounted, a database that does not answer at startup, an invalid config file. The logs with `--previous` say which.
**0** is the container that exited cleanly and should not have exited at all. Check the `command` and the `entrypoint`.
**127** is "command not found", almost always a mistyped `command` or a binary that does not exist in that image.
A very common and hard-to-see cause: the application is fine, but it takes longer to start than the liveness probe tolerates. The probe fails, the kubelet kills the container, and the cycle repeats forever.
You recognise it because the logs show no application error and the exit code is 143. `describe` shows `Liveness probe failed` events.
The right fix is not a longer liveness timeout: it is a `startupProbe`, which exists precisely to allow startup time without loosening later supervision. Raising the liveness `initialDelaySeconds` works but leaves the service unsupervised for that entire window.
This is the part most guides leave out. When a service that was stable for weeks enters CrashLoopBackOff, the application did not corrupt itself: something changed. A new image, an edited ConfigMap, a rotated Secret, a memory limit lowered in a commit last week, a renamed variable.
The practical problem is that this information is scattered. The image is in the Deployment, the commit in the repository, the ConfigMap in another object, and the deployment time in your CI history — if retention has not deleted it.
Rebuilding that sequence by hand in the middle of an incident is what stretches time to recovery, the MTTR. Not because the diagnosis is hard, but because gathering the data takes longer than solving it.
Moonin keeps a revision history per service: which image, which commit, what changed against the previous revision, and what time it was deployed. When a pod enters CrashLoopBackOff, the question "what changed before this" has an answer on one screen instead of four.
It also records restarts and relates them to the revision that was active, so a service that has been restarting since Tuesday is distinguishable from one that started ten minutes ago.
It installs as one Helm chart per cluster and requires no code instrumentation: service-to-service traces come from eBPF in the node kernel. That agent needs node privileges to read kernel traffic; access to the Kubernetes API is read-only.
What it does not do, so there is no surprise: it does not restart anything, does not roll back deployments and takes no action on its own. It tells you what changed; the rollback is yours.
If the pod enters CrashLoopBackOff on only some nodes, look at the node rather than the deployment: memory pressure, a full disk, a different container runtime version, or a different architecture if you have mixed arm64 and amd64 nodes.
If every service depending on the same database enters the state at once, the problem is in the external dependency and no deployment history will show it.
And if the pod never started at all, check that the state really is CrashLoopBackOff and not ImagePullBackOff or Pending: those are different problems with different causes, and confusing them costs you the first half hour.
The demo is open and needs no form, running on a real cluster with sample data: you can look at how the revision history and restarts appear before installing anything.
About usPricingSecurity and permissionsPartner programmevs Datadogvs Prometheus & Grafanavs New Relicvs Dynatracevs ELK and ElasticsearchCrashLoopBackOffOOMKilledImagePullBackOffPod stuck in PendingDocumentationDemoSign upContact usArguz, the consultancy
The wait doubles each attempt: 10 seconds, 20, 40, 80, and so on up to a ceiling of five minutes, where it stays. That is why a pod in CrashLoopBackOff can go a while doing nothing, and the last log line you see may be several minutes old.
Because you are reading the current container, which probably has not started yet or is already dead. Use `kubectl logs <pod> --previous`, which reads the logs of the previous attempt — the one that failed. If the pod has several containers, add `-c <container>`.
That the process received SIGKILL, and almost always it is the kernel killing it over memory: the container exceeded its limit. `kubectl describe pod` confirms it with `Reason: OOMKilled`. Raising the limit unblocks it, but if the cause is a memory leak that only postpones the problem.
No. OOMKilled is one of the reasons a container dies; CrashLoopBackOff is the state of the pod after it died several times. A pod can be in CrashLoopBackOff with exit code 137 (it was OOMKilled) or with exit code 1 (the application failed on its own), and the diagnosis differs.
You need the deployment history: which image and commit were active before and after, and at what time. `kubectl rollout history` gives you Deployment revisions but not the commit or the exact time, and retention is usually short. Moonin keeps that history per service and relates restarts to the revision that was active.