Skip to main content
Kubernetes·8 min read

Pod Lifecycle in Kubernetes — Phases, Probes, and Graceful Termination

Understand the full Kubernetes Pod lifecycle: the five phases, container states, init containers, liveness/readiness/startup probes, restart policies, graceful shutdown, and why you keep seeing CrashLoopBackOff.

DT

DevOps Engineer & Technical Writer

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.

POD LIFECYCLE — PHASE STATE MACHINE Pending scheduling + image pull startup probe ok Running liveness + readiness exit 0 exit != 0 Succeeded all containers done Failed non-zero / killed restartPolicy: Always → back to Pending/Running (CrashLoopBackOff) Unknown = node unreachable · SIGTERM → terminationGracePeriodSeconds → SIGKILL on shutdown

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

PhaseMeaning
<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 reason tells you why: ContainerCreating, ImagePullBackOff, CrashLoopBackOff.
  • Running — the container is executing. Includes a startedAt timestamp.
  • 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:

ProbeQuestion it answersEffect 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 to Succeeded or Failed.

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:

  • The Pod is marked Terminating and removed from Service endpoints (stops receiving new traffic).
  • The optional preStop hook runs.
  • The container gets a SIGTERM — the signal to finish in-flight work and shut down cleanly.
  • The kubelet waits up to terminationGracePeriodSeconds (default 30s).
  • If the container is still alive after the grace period, it gets SIGKILL.
  • 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. Running doesn't mean healthy. Check container states and probe status in describe.
    • 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.