Back to Blog

Pod & Container Lifecycle for the CKAD Exam: postStart, preStop, Graceful Shutdown & restartPolicy

A practitioner's guide to the Kubernetes pod and container lifecycle for the CKAD exam — postStart and preStop hooks, the exact termination sequence, terminationGracePeriodSeconds, restartPolicy, and how to build zero-downtime shutdown that survives rolling updates.

By Sailor Team , August 30, 2026

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.

LevelValueMeaning
Pod phasePendingAccepted, but one or more containers not yet running (pulling images, scheduling)
Pod phaseRunningBound to a node, all containers created, at least one running/starting/restarting
Pod phaseSucceededAll containers terminated successfully, will not restart
Pod phaseFailedAll containers terminated, at least one with non-zero exit or killed
Pod phaseUnknownNode state can’t be obtained (usually node comms failure)
Container stateWaitingNot yet running — pulling image, applying secrets, or in backoff
Container stateRunningExecuting without issues
Container stateTerminatedRan 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:

HandlerWhat it doesTypical use
execRuns a command inside the containerWarm-up, drain, cleanup
httpGetSends an HTTP GET to the containerTrigger an app endpoint
sleepPauses 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:

  1. The Pod is marked Terminating and its API object gets a deletionTimestamp. It is removed from Service endpoints at roughly the same moment, so new traffic stops being routed to it.
  2. If a preStop hook is defined, it runs now — inside the container, blocking.
  3. Once preStop returns, the kubelet sends SIGTERM to PID 1 of each container.
  4. The application is expected to catch SIGTERM and shut down cleanly (finish in-flight work, close connections).
  5. If the container is still running after terminationGracePeriodSeconds, the kubelet sends SIGKILL and 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.

ValueBehaviorWhere it’s used
Always (default)Restart the container whenever it exits, success or failureDeployments, ReplicaSets, DaemonSets, StatefulSets
OnFailureRestart only on non-zero exitJobs, CronJobs (batch work)
NeverNever restart, regardless of exit codeOne-shot Pods, some Jobs

Two rules the exam loves:

  • Controllers that keep Pods running forever — Deployments, ReplicaSets, DaemonSets — require restartPolicy: Always. You cannot set Never on a Deployment’s Pod template.
  • Jobs must use OnFailure or Never, never Always — Always would 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

  • postStart ordering — it does not reliably run before the entrypoint. If something must exist before the app starts, use an init container.
  • tcpSocket in a hook — invalid. Hooks take exec, httpGet, or sleep. tcpSocket is a probe handler only.
  • Grace period includes the hook — a long preStop eats into terminationGracePeriodSeconds; the app may get almost no SIGTERM time.
  • lifecycle is per-container; restartPolicy and terminationGracePeriodSeconds are per-Pod. Watch the indentation level in a YAML question.
  • logs --previous — always use it when investigating a crash loop.
  • A failed postStart kills 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.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now