CertGrid CertGrid
Hands-on Lab·Certified Kubernetes Application Developer

Init Containers and Completion Order

An init container runs to completion before the next one starts, and all of them finish before any application container begins. That makes them the standard place to wait for a dependency, fetch a file or run a migration - and it makes them the standard reason a Pod sits at 0/1 with a STATUS nobody recognises. Both halves are here: the ordering, and the failure.

Application Design and Build Guide 8 of 44 Beginner

Written against the versions above. An init container's `restartPolicy` follows the Pod's. With `restartPolicy: Always` (the default for a bare Pod) a failing init container is retried with backoff forever, which is why the failing Pod below accumulates restarts rather than being marked failed.

Two Pods on the worker nodes. Nothing here depends on which node they land on.
Server NameIP AddressOSRolesCPURAMHDD
CKA1001192.168.0.175Ubuntu 26.04 LTSControl Plane Node2 Core4 GB50 GB
CKA1001-NODE01192.168.0.176Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE02192.168.0.177Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE03192.168.0.178Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB

Before you start

  1. Two init containers, in order

    Each writes a line to a shared volume and sleeps five seconds. Watch the STATUS column change as they go - Init:0/2, then Init:1/2, then PodInitializing, then Running. The fraction is how many init containers have finished.

    When the app container finally starts, it reads the file they left:

    first
    second

    In order. first completed before second started - init containers are strictly sequential, unlike application containers which all start together. That is the property you rely on when one has to fetch something the next one needs.

    bash Example session
    kubectl create namespace ckad-initnamespace/ckad-init createdsleep 4; kubectl -n ckad-init get pod appNAME   READY   STATUS     RESTARTS   AGEapp    0/1     Init:0/2   0          4ssleep 8; kubectl -n ckad-init get pod appNAME   READY   STATUS    RESTARTS   AGEapp    1/1     Running   0          13skubectl -n ckad-init wait --for=condition=Ready pod/app --timeout=120spod/app condition metkubectl -n ckad-init logs appDefaulted container "app" out of: app, first (init), second (init)firstsecond

    Expected resultThe Pod passes through Init states and the app prints both lines in order.

    Success conditionYou have seen sequencing you can rely on for a dependency or a migration.

  2. The app container did not start until they were done

    INIT           DONE                  APP
    first,second   Completed,Completed   2026-08-23T03:05:16Z

    Both init containers Completed, and only then does the app container have a startedAt. This is the guarantee: an application container cannot observe a half-finished init sequence, because it does not exist yet.

    It is also the cost. Init containers are on the critical path of every Pod start, including every rollout and every restart. A thirty-second init container adds thirty seconds to each of those.

    bash Example session
    kubectl -n ckad-init get pod app -o 'custom-columns=INIT:.status.initContainerStatuses[*].name,DONE:.status.initContainerStatuses[*].state.terminated.reason,APP:.status.containerStatuses[*].state.running.startedAt'INIT           DONE                  APPfirst,second   Completed,Completed   2026-08-23T03:05:16Z

    Expected resultTwo Completed init containers and one start time after them.

    Success conditionYou can prove the ordering from status rather than from timing.

  3. One that fails

    This init container resolves a Service that was never created. After thirty seconds:

    NAME      READY   STATUS       RESTARTS      AGE
    blocked   0/1     Init:Error   2 (29s ago)   30s

    Init:Error, and the phase is still Pending - not Running, not Failed. The application container has never been created. Anyone looking for a crash in the app's logs will find nothing at all, because there is nothing there.

    The restart count is on the *init* container, and it is climbing, because the Pod's default restartPolicy: Always retries it with backoff indefinitely.

    The logs are where the actual reason is, and you have to ask for the init container by name:

    ** server can't find does-not-exist.ckad-init.svc.cluster.local: NXDOMAIN
    bash Example session
    sleep 30; kubectl -n ckad-init get pod blockedNAME      READY   STATUS       RESTARTS      AGEblocked   0/1     Init:Error   2 (29s ago)   30skubectl -n ckad-init get pod blocked -o 'custom-columns=PHASE:.status.phase,INIT:.status.initContainerStatuses[*].state.waiting.reason,RESTARTS:.status.initContainerStatuses[*].restartCount'PHASE     INIT     RESTARTSPending   <none>   2kubectl -n ckad-init logs blocked -c waitfor --tail=6Address:	10.96.0.10:53 ** server can't find does-not-exist.ckad-init.svc.cluster.local: NXDOMAIN ** server can't find does-not-exist.ckad-init.svc.cluster.local: NXDOMAIN

    Expected resultInit:Error, phase Pending, restarts climbing, and NXDOMAIN in the logs.

    Success conditionYou can recognise a Pod that is stuck before it ever started.

  4. Where the events point

    Normal   Scheduled  30s   default-scheduler  Successfully assigned ckad-init/blocked to cka1001-node01
    Normal   Pulled     18s (x3 over 30s)  kubelet  spec.initContainers{waitfor}: Container image "busybox:1.36" already present on machine

    Note the prefix: spec.initContainers{waitfor}. Events name which container they are about, and that prefix is the fastest way to tell an init failure from an application failure when you are reading describe output under time pressure.

    The x3 is the retry count in event form.

    The debugging order for a Pod stuck at Init::

    1. kubectl get pod - the fraction says which init container
    2. kubectl logs -c - the real error
    3. kubectl describe pod - if the logs are empty, the image or the volume is the problem
    bash Example session
    kubectl -n ckad-init describe pod blocked | grep -A4 "Events:" | head -8Events:  Type     Reason     Age                From               Message  ----     ------     ----               ----               -------  Normal   Scheduled  30s                default-scheduler  Successfully assigned ckad-init/blocked to cka1001-node01  Normal   Pulled     18s (x3 over 30s)  kubelet            spec.initContainers{waitfor}: Container image "busybox:1.36" already present on machine and can be accessed by the podkubectl delete namespace ckad-init --wait=falsenamespace "ckad-init" deleted

    Expected resultEvents prefixed with the init container's name.

    Success conditionYou have a three-step routine for an Init failure.

Troubleshooting

Official sources