Most CKAD candidates can write a Pod spec in their sleep, but freeze when a question asks them to “drain in-flight requests before the container stops” or “run a warm-up command after the container starts.” Those are lifecycle problems, and they map to a small set of fields — lifecycle.postStart, lifecycle.preStop, terminationGracePeriodSeconds, and restartPolicy — that the exam tests precisely because they separate people who use Kubernetes from people who have only read about it.
This guide walks through the full pod and container lifecycle from a practitioner’s angle: what actually happens between kubectl apply and a container serving traffic, what happens during termination, and how to wire up hooks so a rolling update never drops a request. If you’re still assembling the fundamentals, start with the CKAD Exam Guide 2026 and the multi-container Pod patterns article, then come back here — lifecycle hooks and init containers are close cousins.
The Lifecycle Big Picture
A Pod moves through a small set of phases, and each container inside it moves through its own states. Knowing the vocabulary is half the battle when you read kubectl describe pod output under time pressure.
| Level | Value | Meaning |
|---|---|---|
| Pod phase | Pending | Accepted, but one or more containers not yet running (pulling images, scheduling) |
| Pod phase | Running | Bound to a node, all containers created, at least one running/starting/restarting |
| Pod phase | Succeeded | All containers terminated successfully, will not restart |
| Pod phase | Failed | All containers terminated, at least one with non-zero exit or killed |
| Pod phase | Unknown | Node state can’t be obtained (usually node comms failure) |
| Container state | Waiting | Not yet running — pulling image, applying secrets, or in backoff |
| Container state | Running | Executing without issues |
| Container state | Terminated | Ran and finished, or failed; has an exit code and reason |
You read container states in the State: and Last State: fields of kubectl describe pod. A container stuck in Waiting with reason CrashLoopBackOff is the single most common thing the exam asks you to diagnose — more on that below and in the application troubleshooting guide.
Startup Order: Init Containers Then App Containers
Before any application container starts, init containers run to completion, one at a time, in the order declared. Each must exit 0 before the next begins. Only after every init container finishes do the app containers start — and they start in parallel. Init containers are covered in depth in the multi-container Pod patterns article; the lifecycle point to remember is that they are a hard gate. A Pod sitting in Init:0/2 is telling you the first of two init containers hasn’t succeeded yet.
Once app containers are created, each can fire a postStart hook.
Container Lifecycle Hooks: postStart and preStop
Kubernetes gives each container two hooks, declared under lifecycle: in the container spec:
postStart— runs immediately after the container is created.preStop— runs immediately before the container is terminated.
Each hook uses one of these handlers:
| Handler | What it does | Typical use |
|---|---|---|
exec | Runs a command inside the container | Warm-up, drain, cleanup |
httpGet | Sends an HTTP GET to the container | Trigger an app endpoint |
sleep | Pauses for N seconds (GA in recent releases) | Simple drain delay |
Note that tcpSocket is not a valid lifecycle hook handler — that’s a probe handler. Mixing those up is a classic trap; if you’re shaky on probes, review liveness, readiness & startup probes separately.
postStart: the “runs after start” trap
The name postStart is misleading. The hook fires after the container is created, but Kubernetes makes no guarantee that it runs before the container’s ENTRYPOINT. The two execute concurrently. So you cannot use postStart to reliably prepare something the main process needs at boot — that’s what init containers are for.
What postStart is good for: side effects that can happen alongside the app, like registering the instance somewhere or touching a marker file. Critically, if the postStart hook fails (non-zero exit or HTTP error), the container is killed and subject to the Pod’s restartPolicy.
apiVersion: v1
kind: Pod
metadata:
name: hooks-demo
spec:
containers:
- name: web
image: nginx:1.27
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo 'started' > /usr/share/nginx/html/ready.txt"]
preStop:
exec:
command: ["/bin/sh", "-c", "nginx -s quit; sleep 5"]
preStop: the hook that makes shutdown graceful
preStop runs before the container receives SIGTERM. It is blocking — the SIGTERM is not sent until the hook returns (or the grace period runs out). This is exactly where you drain connections, deregister from a service, or give a load balancer time to stop routing new traffic to the Pod.
The most common, universally-portable pattern is a short sleep that lets endpoint removal propagate:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
On clusters that support the native sleep action, the same intent is cleaner:
lifecycle:
preStop:
sleep:
seconds: 15
Use the exec + sleep command form when you want maximum compatibility across cluster versions — it works everywhere and there’s nothing to remember about feature gates on exam day.
The Termination Sequence (Memorize This)
When a Pod is deleted — directly, or because a rolling update or scale-down evicted it — the following happens. Understanding the order is the single most valuable lifecycle fact for the exam:
- The Pod is marked
Terminatingand its API object gets adeletionTimestamp. It is removed from Service endpoints at roughly the same moment, so new traffic stops being routed to it. - If a
preStophook is defined, it runs now — inside the container, blocking. - Once
preStopreturns, the kubelet sendsSIGTERMto PID 1 of each container. - The application is expected to catch
SIGTERMand shut down cleanly (finish in-flight work, close connections). - If the container is still running after
terminationGracePeriodSeconds, the kubelet sendsSIGKILLand the process dies immediately.
The subtle, heavily-tested detail: the grace-period countdown starts at step 1, not at step 3. The clock runs through your preStop hook. So if terminationGracePeriodSeconds is 30 and your preStop sleeps for 25, your app only gets ~5 seconds of SIGTERM handling before SIGKILL. Size the grace period to cover both the hook and the app’s own shutdown.
delete ──► Terminating + removed from endpoints ──► preStop hook ──► SIGTERM ──► (grace period) ──► SIGKILL
└──────────────────── terminationGracePeriodSeconds clock runs across all of this ─────────┘
terminationGracePeriodSeconds
This field lives at the Pod spec level (not per-container) and defaults to 30 seconds.
apiVersion: v1
kind: Pod
metadata:
name: graceful
spec:
terminationGracePeriodSeconds: 45
containers:
- name: app
image: myapp:1.0
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
You can override it at delete time:
# Give the Pod 60 seconds instead of its spec value
kubectl delete pod graceful --grace-period=60
# Force immediate deletion (skips graceful shutdown entirely)
kubectl delete pod graceful --grace-period=0 --force
Reach for --grace-period=0 --force only when a Pod is genuinely stuck — it can leave the container running on the node in edge cases. It’s a troubleshooting tool, not a normal shutdown.
Why This Matters for Zero-Downtime Rolling Updates
Here’s the real-world scenario the exam dresses up in different clothes. During a rolling update, Kubernetes deletes old Pods and creates new ones. Two things happen when an old Pod is deleted: it’s removed from the Service endpoints and it gets SIGTERM. These are asynchronous and racy — a load balancer or kube-proxy on another node might still send a request to the Pod for a brief window after SIGTERM.
The fix is a preStop sleep that holds the container open long enough for endpoint removal to propagate everywhere before the app actually stops:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: api
image: myapp:2.0
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"] # let endpoint removal propagate
Combined with a correct readiness probe on the new Pods (so traffic only shifts once they’re actually ready), this is the canonical zero-downtime deployment recipe. If a question says “requests fail for a few seconds during every deploy,” a preStop drain delay plus readiness gating is almost always the intended answer.
restartPolicy: Always, OnFailure, Never
restartPolicy is set at the Pod spec level and applies to all containers in the Pod. It controls whether the kubelet restarts a container that exits.
| Value | Behavior | Where it’s used |
|---|---|---|
Always (default) | Restart the container whenever it exits, success or failure | Deployments, ReplicaSets, DaemonSets, StatefulSets |
OnFailure | Restart only on non-zero exit | Jobs, CronJobs (batch work) |
Never | Never restart, regardless of exit code | One-shot Pods, some Jobs |
Two rules the exam loves:
- Controllers that keep Pods running forever — Deployments, ReplicaSets, DaemonSets — require
restartPolicy: Always. You cannot setNeveron a Deployment’s Pod template. - Jobs must use
OnFailureorNever, neverAlways—Alwayswould defeat the point of a Job that’s supposed to complete.
CrashLoopBackOff and the backoff timer
When a container with restartPolicy: Always keeps crashing, the kubelet restarts it with an exponential back-off: roughly 10s, 20s, 40s, and so on, capped at 5 minutes (300s). While waiting, the container sits in Waiting with reason CrashLoopBackOff. The timer resets after the container runs successfully for ~10 minutes.
CrashLoopBackOff is not a root cause — it’s a symptom. Diagnose it with:
kubectl describe pod <name> # look at Last State, Exit Code, Reason
kubectl logs <name> --previous # logs from the crashed instance, not the current one
The --previous flag is the trick most candidates forget: by the time you look, the current container may be freshly restarted and empty, so you need the previous container’s logs. Keep the kubectl cheat sheet handy for exactly these flags.
Putting It All Together
Here’s a Pod that exercises the whole lifecycle — init gate, startup side effect, graceful drain, and a sane grace period:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-complete
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 40
initContainers:
- name: wait-for-config
image: busybox:1.36
command: ["sh", "-c", "until [ -f /config/ready ]; do sleep 1; done"]
volumeMounts:
- name: config
mountPath: /config
containers:
- name: app
image: myapp:1.0
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo booted > /tmp/started"]
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10 && /app/drain.sh"]
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
emptyDir: {}
Trace it: the init container blocks until config appears; the app container starts and fires postStart alongside its entrypoint; on deletion, the Pod leaves the Service, preStop sleeps 10s then drains, SIGTERM follows, and the app has the remainder of the 40-second window to finish before SIGKILL.
Common Exam Traps
postStartordering — it does not reliably run before the entrypoint. If something must exist before the app starts, use an init container.tcpSocketin a hook — invalid. Hooks takeexec,httpGet, orsleep.tcpSocketis a probe handler only.- Grace period includes the hook — a long
preStopeats intoterminationGracePeriodSeconds; the app may get almost noSIGTERMtime. lifecycleis per-container;restartPolicyandterminationGracePeriodSecondsare per-Pod. Watch the indentation level in a YAML question.logs --previous— always use it when investigating a crash loop.- A failed
postStartkills the container — it’s not fire-and-forget.
Practice Pod Lifecycle Before Exam Day
Lifecycle questions reward muscle memory: you should be able to add a preStop drain, set a grace period, and diagnose a CrashLoopBackOff without pausing to think about field placement. The Certified Kubernetes Application Developer (CKAD) Mock Exam Bundle drills exactly these scenarios — hook placement, termination ordering, restart policies, and the describe/logs --previous debugging flow — under realistic time pressure, with explanations that reinforce why each answer is correct. Pair it with the CKAD 7-Day Revision Plan to convert “I’ve read about hooks” into “I can wire up graceful shutdown in ninety seconds.” For broader exam-day speed technique, the CKAD exam tips are worth a read too.
Frequently Asked Questions
What is the difference between postStart and an init container?
An init container runs to completion before app containers start and is a hard gate — perfect for setup the app depends on. postStart runs after the container is created but concurrently with its entrypoint, with no ordering guarantee, so it’s for side effects, not prerequisites.
Does the preStop hook run before or after SIGTERM?
Before. preStop runs first and blocks; only when it returns (or the grace period expires) does the kubelet send SIGTERM to the container’s main process.
Why does my app get killed even though terminationGracePeriodSeconds is 30?
Because the grace-period clock starts when termination begins — including the time your preStop hook runs. If preStop consumes 25 of 30 seconds, the app only gets ~5 seconds after SIGTERM before SIGKILL. Increase the grace period to cover both.
Which restartPolicy values are valid for a Deployment?
Only Always. Deployments, ReplicaSets, and DaemonSets require restartPolicy: Always. OnFailure and Never are for Jobs and one-shot Pods.
How do I see logs from a container that keeps crashing?
Use kubectl logs <pod> --previous to read the logs of the previous, crashed container instance. The current instance may have just restarted and produced nothing yet.
Can I use tcpSocket in a lifecycle hook?
No. Lifecycle hooks support exec, httpGet, and (on recent clusters) sleep. tcpSocket is a probe handler, not a hook handler — using it in a lifecycle block is invalid.
What causes CrashLoopBackOff?
It’s a symptom, not a cause: a container keeps exiting and the kubelet keeps restarting it with an exponential back-off (up to 5 minutes). Inspect the exit code with kubectl describe pod and read kubectl logs --previous to find the real reason — a bad command, missing config, or failed dependency.