
September 02, 2026
8 min read
Table of Contents
By Kokil Thapa | Last reviewed: September 2026
Debugging a stuck Kubernetes workload usually comes down to misunderstanding Pod Lifecycle and Restart Policies. When a container exits unexpectedly or gets stuck in a pending state, the kubelet’s response is dictated entirely by these two interacting mechanisms. If you have ever stared at a CrashLoopBackOff status without knowing why the cluster keeps restarting your app, this guide explains the exact mechanics at play. For a broader look at cluster debugging, my article on debugging CrashLoopBackOff in Kubernetes covers specific troubleshooting workflows that complement this foundational knowledge.
What Are the Core Stages of Pod Lifecycle and Restart Policies?
A pod is not a single atomic unit; it is a sequence of distinct phases managed by the kubelet. Understanding Pod Lifecycle and Restart Policies requires separating the pod-level phase from individual container states. The pod phase is a high-level summary visible via kubectl get pods, but the actual restart decisions happen at the container level based on exit codes.
The five primary pod phases are:
- Pending: The pod has been accepted by the API server, but one or more containers are not yet running. This includes time spent scheduling onto a node and pulling images. In my experience with Nepal-based infrastructure where bandwidth can be variable, large image pulls often extend this phase significantly.
- Running: The pod is bound to a node, all containers have been created, and at least one container is still executing. Note that "Running" does not mean "healthy"—it simply means the process has started. Readiness probes determine traffic eligibility separately.
- Succeeded: All containers terminated with exit code 0 and will not be restarted. This is typical for Jobs and CronJobs.
- Failed: All containers have terminated, and at least one exited with a non-zero code or was terminated by the system. The restart policy determines if this state triggers a new attempt.
- Unknown: The API server cannot communicate with the kubelet on the node hosting the pod. This usually indicates network partitioning or node failure rather than an application issue.
Critically, container states within a pod include Waiting, Running, and Terminated. A pod can report Running even if its main container is in Waiting state due to a missing ConfigMap or Secret. Always inspect individual container statuses with kubectl describe pod rather than relying solely on the phase column.
How Do RestartPolicy Options Control Container Recovery?
The restartPolicy field in the pod spec applies to all containers in the pod and is evaluated only when a container terminates. It does not affect liveness probe failures directly—those always trigger a restart regardless of policy—but governs what happens after a clean or unclean exit.
| RestartPolicy | Behavior | Best For | Controller Compatibility |
|---|---|---|---|
Always | Restarts container on any exit code (0 or non-zero) | Long-running web servers, APIs, daemons | Deployments, ReplicaSets, DaemonSets (default) |
OnFailure | Restarts only on non-zero exit code | Data processing jobs, migrations, scripts | Jobs, CronJobs |
Never | Never restarts; pod enters Failed/Succeeded permanently | One-shot tasks, audit logs, forensic captures | Jobs (manual inspection required) |
A common mistake I see in production Laravel applications deployed to Kubernetes is using restartPolicy: Always for queue worker pods that should gracefully drain and exit. When the queue empties and the worker exits cleanly (code 0), Always forces an immediate restart, wasting resources. For workers designed to process a batch and stop, OnFailure ensures they only restart if something actually broke.
The restart backoff algorithm prevents runaway loops. After each restart, the kubelet waits 10 seconds, then doubles the delay up to a maximum of 5 minutes. This exponential backoff resets only after 10 minutes of successful execution. If your container crashes every 8 minutes, the backoff never resets, and you enter CrashLoopBackOff permanently. Understanding this timing is essential for diagnosing intermittent failures in Pod Lifecycle and Restart Policies.
How Do Probes Interact With Pod Lifecycle and Restart Policies?
Probes do not replace restart policies—they layer additional health semantics on top of them. Misconfiguring probes is the most frequent cause of unnecessary restarts in otherwise healthy applications. There are four probe types in Kubernetes 1.32+ (stable in 2026):
- startupProbe: Disables other probes until it succeeds. Essential for legacy PHP/Laravel apps with slow boot times. Without this, liveness checks kill the pod before initialization completes.
- livenessProbe: Determines if the container should be restarted. Failure triggers a kill and subject to
restartPolicy. Use sparingly; a false positive causes cascading restarts. - readinessProbe: Controls endpoint inclusion in Services. Failure removes the pod from load balancer rotation but does not restart it.
- sleepProbe: Newer addition for graceful shutdown coordination; less commonly used in standard web workloads.
For a Laravel application with heavy service provider bootstrapping, a practical configuration uses a generous startup probe to absorb cold starts, a conservative liveness probe checking /up (Laravel 11+ built-in health endpoint), and a readiness probe verifying database connectivity. Setting liveness initialDelaySeconds too low without a startup probe guarantees restart storms during deployment.
<!-- Example: Safe probe config for Laravel 12 -->
startupProbe:
httpGet:
path: /up
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /up
port: 8080
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /api/health
port: 8080
periodSeconds: 5 Note that probe failures bypass restartPolicy: Never for liveness checks. Even with Never, a failed liveness probe kills the container. Only exit-code-driven restarts respect the policy strictly. This distinction trips up many engineers configuring batch jobs that must not auto-restart but still need health monitoring during execution.
Why Does CrashLoopBackOff Happen Under Specific Restart Policies?
CrashLoopBackOff is not a pod phase—it is a container status reason indicating the kubelet is applying exponential backoff after repeated failures under restartPolicy: Always or OnFailure. It signals that the system is functioning as designed, but the application cannot sustain execution.
Common root causes tied to lifecycle misunderstandings include:
- Missing environment variables or secrets: Container starts, reads config, exits immediately with code 1. Backoff escalates quickly because runtime is near-zero.
- Liveness probe misconfiguration: App takes 45 seconds to boot, but liveness checks start at 10 seconds. Container is killed before ready, restarts, killed again, entering backoff loop.
- Resource exhaustion: OOMKilled exits appear as code 137. If memory limits are too tight for peak usage, every restart hits the same ceiling.
- Application logic errors: Unhandled exceptions in CLI commands or queue workers cause immediate exits. Unlike HTTP servers that catch errors per-request, batch processes die entirely.
To diagnose, always check kubectl get events --sort-by=.lastTimestamp alongside pod description. Events reveal whether the kill came from a probe failure, OOM condition, or natural exit. For deeper troubleshooting patterns including log inspection and event correlation, refer to debugging CrashLoopBackOff in Kubernetes. On real client projects running Laravel queue workers, I have found that adding structured logging at the very first line of the command handler catches configuration errors before framework bootstrap fails silently.
How Should You Configure Pod Lifecycle and Restart Policies for Production Workloads?
Production configuration requires matching restart semantics to workload type. There is no universal setting. Deployments serving HTTP traffic should almost always use restartPolicy: Always with properly tuned probes. Batch Jobs benefit from OnFailure with explicit backoffLimit to prevent infinite retries. One-off administrative tasks should use Never to preserve failure evidence for manual review.
Key production practices:
- Set resource requests and limits: Without limits, a single leaking container can destabilize the node, causing unrelated pods to be evicted and restarted unpredictably.
- Use preStop hooks for graceful shutdown: HTTP servers need time to drain connections. A 5-second
preStopsleep plusterminationGracePeriodSeconds: 30prevents dropped requests during rolling updates. - Avoid latest tags: Image pull behavior changes between
IfNotPresentandAlways. Pin versions to ensure restarts use identical artifacts, eliminating "works after restart" mysteries caused by silent image updates. - Monitor restart counts: Alert on
kube_pod_container_status_restarts_totalexceeding thresholds. Frequent restarts indicate underlying issues even if the pod eventually stabilizes.
For teams managing multiple environments, consider reading about Kubernetes resource limits and requests to pair resource governance with restart policies effectively. Proper resource sizing reduces OOM-induced restarts by 60–80% in my experience with PHP workloads, which tend to have higher baseline memory footprints than Go or Rust services.
Practical Takeaways for Managing Pod Lifecycle and Restart Policies
Mastering Pod Lifecycle and Restart Policies separates operators who fight fires from those who design resilient systems. Start by auditing every pod spec in your cluster: verify restart policies match workload intent, confirm probes have appropriate thresholds, and ensure resource limits reflect actual usage patterns. Test failure modes deliberately in staging—kill containers, exhaust memory, simulate slow startups—to validate recovery behavior before production incidents force the lesson. If you need hands-on assistance tuning Kubernetes workloads for Laravel, Symfony, or custom PHP applications, reach out to discuss your infrastructure. Stable pods are built through intentional configuration, not hopeful defaults.









