The Problem
A Pod shows CrashLoopBackOff and the usual reaction is to delete it and hope. But a Pod moves through a well-defined set of phases and container states, and almost every weird status — stuck Pending, flapping readiness, killed mid-request — maps to a specific stage of that lifecycle. Learn the state machine once and most Pod problems become readable instead of mysterious.
A Pod's status.phase is a high-level summary of where it is in life. Underneath it, each container has its own finer-grained state, and probes constantly influence whether traffic flows and whether a container gets restarted.
The Five Pod Phases
| Phase | Meaning |
|---|---|
| <code class="inline-code">Pending</code> | Accepted by the cluster but not yet running — waiting on scheduling, image pulls, or volume mounts. |
| <code class="inline-code">Running</code> | Bound to a node, all containers created, at least one is running or starting/restarting. |
| <code class="inline-code">Succeeded</code> | All containers terminated successfully (exit 0) and will not restart. |
| <code class="inline-code">Failed</code> | All containers terminated and at least one failed (non-zero exit or killed). |
| <code class="inline-code">Unknown</code> | The node hosting the Pod is unreachable, so its state can't be determined. |
Phase is deliberately coarse. For real debugging you look at the container states inside the Pod.
Container States
Each container reports one of three states, visible in kubectl describe pod:
- Waiting — not yet running. The
reasontells you why:ContainerCreating,ImagePullBackOff,CrashLoopBackOff. - Running — the container is executing. Includes a
startedAttimestamp. - Terminated — the container finished or was killed. Includes exit code, reason (
Completed,OOMKilled,Error), and timestamps.
Init Containers Run First
Init containers run to completion, in order, before any app container starts. While they run, the Pod stays in Pending with a status like Init:0/2. They're ideal for setup work — waiting on a dependency, running a migration, fetching config.
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done']
containers:
- name: app
image: myapp:1.4.0
If an init container fails, the Pod restarts it according to the restart policy and the app containers never start — a common cause of a Pod stuck in Init:Error or Init:CrashLoopBackOff.
Probes Shape the Lifecycle
Probes are periodic health checks the kubelet runs against each container. There are three, and they do different jobs:
| Probe | Question it answers | Effect on failure |
|---|---|---|
| <code class="inline-code">startupProbe</code> | Has the app finished starting? | Delays the other probes; on failure (after threshold) the container is killed and restarted. |
| <code class="inline-code">livenessProbe</code> | Is the app still healthy? | Container is killed and restarted. |
| <code class="inline-code">readinessProbe</code> | Can the app serve traffic right now? | Pod is removed from Service endpoints — no restart. |
spec:
containers:
- name: app
image: myapp:1.4.0
ports:
- containerPort: 8080
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30
periodSeconds: 5 # allow up to 150s to start
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
The key distinction: a failing liveness probe restarts the container; a failing readiness probe just pulls it out of load balancing until it recovers. Use a startup probe for slow-booting apps so liveness doesn't kill them before they're up.
Restart Policies
spec.restartPolicy controls what happens when a container exits:
Always(default) — restart regardless of exit code. Used for long-running services (Deployments).OnFailure— restart only on non-zero exit. Common for Jobs.Never— don't restart. The Pod goes toSucceededorFailed.
When a container with Always keeps crashing, the kubelet backs off exponentially between restarts (10s, 20s, 40s… capped at 5 minutes). That backoff state is exactly what CrashLoopBackOff means.
Graceful Termination
When a Pod is deleted or evicted, Kubernetes does not just kill it. It runs an orderly shutdown:
Terminating and removed from Service endpoints (stops receiving new traffic).preStop hook runs.terminationGracePeriodSeconds (default 30s).spec:
terminationGracePeriodSeconds: 45
containers:
- name: app
image: myapp:1.4.0
lifecycle:
preStop:
exec:
command: ['sh', '-c', 'sleep 5'] # let the LB deregister first
Your app should trap SIGTERM, stop accepting new work, drain existing requests, and exit. Apps that ignore SIGTERM get hard-killed and drop requests.
Reading Lifecycle State
# Phase, restarts, and age at a glance
kubectl get pod myapp -o wide
# Container states, probe results, events — the real debugging view
kubectl describe pod myapp
# Why did it die last time?
kubectl logs myapp --previous
Common Mistakes to Avoid
- Using a liveness probe as a readiness probe. A heavy liveness check that fails under load will restart a container that was merely busy. Use readiness to shed traffic; reserve liveness for truly stuck processes.
- No startup probe on slow apps. Without one, the liveness probe kills the container before it finishes booting, producing a permanent
CrashLoopBackOff. - Ignoring SIGTERM. If your process doesn't handle SIGTERM, it gets SIGKILLed and drops in-flight requests on every deploy.
- Reading only the phase.
Runningdoesn't mean healthy. Check container states and probe status indescribe. - Grace period too short for long requests. If requests take longer than
terminationGracePeriodSeconds, they get cut off. Size the grace period to your real request duration.
FAQ
What does CrashLoopBackOff actually mean?
It means a container keeps exiting and the kubelet is deliberately waiting (backing off) longer and longer before each restart attempt. It is a symptom, not a cause — check kubectl logs --previous and kubectl describe pod to find why the container exits, which is usually a bad config, a missing dependency, or a failing startup/liveness probe.
What is the difference between a liveness and a readiness probe?
A failing liveness probe causes the kubelet to kill and restart the container. A failing readiness probe simply removes the Pod from Service endpoints so it stops receiving traffic, without restarting it. Use readiness for "temporarily can't serve" and liveness for "permanently stuck, needs a restart."
Why is my Pod stuck in Pending?
Pending means the Pod is accepted but not yet running. The most common causes are no node with enough free resources, unsatisfiable node selectors or taints, or a volume that can't be mounted. kubectl describe pod shows scheduler events that explain exactly what it's waiting on.
How long does Kubernetes wait before force-killing a Pod?
It waits up to terminationGracePeriodSeconds (default 30 seconds) after sending SIGTERM. If the container is still running when that window closes, Kubernetes sends SIGKILL. Set this value to comfortably exceed how long your app needs to drain in-flight work.