KubeGlanceDownload
Init:0/2, Init:CrashLoopBackOff, Init:ErrImagePull: the Init: prefix in kubectl's STATUS column

kubectl

Init:0/2, Init:CrashLoopBackOff, Init:ErrImagePull: the Init: prefix in kubectl's STATUS column

Init:ErrImagePull, Init:CrashLoopBackOff and Init:0/2 all mean an init container is blocking the pod. How the Init: status is built, and how to read it.

· 9 min read

Any STATUS that starts with Init: means the pod has not finished running its init containers, so none of your application containers have started yet. The part after the colon is about the init container, not your app: Init:0/2 is a progress count, and Init:CrashLoopBackOff, Init:Error or Init:ErrImagePull is the reason the first unfinished init container is stuck.

The prefix is there to tell you where to look. ErrImagePull means your app’s image failed to pull. Init:ErrImagePull means an init container’s image did, and the app’s image has not even been tried.

The fastest check

List every init container with its current reason, exit code and message:

kubectl get pod <pod> -o jsonpath='{range .status.initContainerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{.state.terminated.reason}{"\t"}{.state.terminated.exitCode}{"\t"}{.state.waiting.message}{"\n"}{end}'

For a pod whose init image tag has a typo:

migrate	ImagePullBackOff		Back-off pulling image "busybox:1.36-typo": ErrImagePull: rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/busybox:1.36-typo": failed to resolve reference "docker.io/library/busybox:1.36-typo": docker.io/library/busybox:1.36-typo: not found

For a pod whose first init container exits 1:

wait-for-db	Error	1	
migrate	PodInitializing		

The first line that is not a clean exit is the one kubectl reported. Everything after it reads PodInitializing, which for an init container just means “not my turn yet”.

Every Init: status, reproduced

All of these were produced on a Kubernetes 1.36.1 kind cluster with containerd 2.3.1, using busybox init containers in front of an nginx app.

A pod with two slow init containers (sleep 20 each), watched from creation:

kubectl get pod slow-init -w
NAME        READY   STATUS     RESTARTS   AGE
slow-init   0/1     Init:0/2   0          1s
slow-init   0/1     Init:1/2   0          21s
slow-init   0/1     PodInitializing   0          43s
slow-init   1/1     Running           0          43s

A pod whose first init container exits 1, with the default restartPolicy: Always:

NAME         READY   STATUS                  RESTARTS      AGE
init-fails   0/1     Init:Error              1 (1s ago)    2s
init-fails   0/1     Init:CrashLoopBackOff   1 (14s ago)   15s
init-fails   0/1     Init:0/2                2 (14s ago)   15s
init-fails   0/1     Init:Error              2 (15s ago)   16s
init-fails   0/1     Init:CrashLoopBackOff   2 (24s ago)   39s

It cycles: Init:Error when the container exits, Init:CrashLoopBackOff while the kubelet waits out the backoff, and Init:0/2 for the moment it is running again. The count never moves past 0, because the kubelet reruns the same container rather than moving on.

The same failure with restartPolicy: Never stops at Init:Error for good, and the pod’s phase becomes Failed. There is no retry to wait for.

A missing ConfigMap referenced from an init container’s env:

NAME              READY   STATUS                            RESTARTS   AGE
init-missing-cm   0/1     Init:CreateContainerConfigError   0          8s

Any waiting reason can appear after the prefix. The message behind that one is configmap "db-config" not found, exactly as it would be for an app container.

Init:ErrImagePull and Init:ImagePullBackOff

This is the one people search for, and it is the plainest of the lot: the image named by an init container could not be pulled. Watched from creation:

NAME             READY   STATUS                  RESTARTS   AGE
init-bad-image   0/1     Init:0/1                0          0s
init-bad-image   0/1     Init:ErrImagePull       0          4s
init-bad-image   0/1     Init:ImagePullBackOff   0          17s
init-bad-image   0/1     Init:ErrImagePull       0          32s
init-bad-image   0/1     Init:ImagePullBackOff   0          46s

Init:ErrImagePull is the attempt that just failed; Init:ImagePullBackOff is the wait before the next one. The events carry the reason, and the reason is the same list as for any other image: wrong tag, missing registry credentials, private image, wrong platform, rate limit.

kubectl events --for pod/init-bad-image
LAST SEEN             TYPE      REASON      OBJECT               MESSAGE
28s                   Warning   Failed      Pod/init-bad-image   Failed to pull image "busybox:1.36-typo": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/busybox:1.36-typo": failed to resolve reference "docker.io/library/busybox:1.36-typo": docker.io/library/busybox:1.36-typo: not found
28s                   Warning   Failed      Pod/init-bad-image   Error: ErrImagePull
27s                   Normal    BackOff     Pod/init-bad-image   Back-off pulling image "busybox:1.36-typo"
27s                   Warning   Failed      Pod/init-bad-image   Error: ImagePullBackOff
13s (x2 over 4m26s)   Normal    Pulling     Pod/init-bad-image   Pulling image "busybox:1.36-typo"

Everything in ImagePullBackOff and ErrImagePull applies unchanged, including how to read each registry message and fix it. The only init-specific points are these:

  • Init containers are often the image nobody looks at. They tend to come from a Helm chart’s defaults (busybox, a wait-for image, a migration tool) and point at Docker Hub while your app points at a private registry. A pod whose imagePullSecrets cover the app’s registry can still fail on the init image.
  • A slow init pull does not show as a pull. While the image is still downloading, the init container’s waiting reason is PodInitializing, and kubectl deliberately skips that reason, so STATUS reads Init:0/1. On a node pulling something large, a pod can sit at Init:0/1 for minutes with nothing wrong. kubectl events will show Pulling with no Failed after it.
  • Fixing it does not need a new pod. The kubelet keeps retrying, with the backoff capped at five minutes, so correcting the tag in a Deployment rolls a new pod anyway, and fixing the credentials lets a bare pod recover on the next retry.

How kubectl builds the Init: string

kubectl get pods does not compute STATUS itself. It asks the API server for a Table (Accept: application/json;as=Table;v=v1;g=meta.k8s.io at -v=8), and the API server fills the STATUS cell by running printPod in pkg/printers/internalversion/printers.go. So the rules that follow are your API server’s version, not your kubectl’s. The function is identical on release-1.36 and release-1.37, 43 lines further down on 1.37; line numbers below are 1.36 / 1.37.

It walks status.initContainerStatuses in order, and stops at the first one that is not finished (lines 965 / 1008):

  1. Terminated with exit code 0: done, move to the next.
  2. A sidecar that has started: also counts as done (986 / 1029). More below.
  3. Terminated non-zero: Init: plus the terminated reason, which with containerd is usually Init:Error (1001 / 1044). If the runtime supplied no reason, it falls back to Init:Signal:N or Init:ExitCode:N (996–998 / 1039–1041).
  4. Waiting with any reason except PodInitializing: Init: plus that reason, which is where Init:CrashLoopBackOff, Init:ErrImagePull and Init:CreateContainerConfigError come from (1004–1005 / 1047–1048).
  5. Anything else, meaning running or waiting its turn: Init:N/M, where N is the position of this container and M is len(pod.Spec.InitContainers) (1008 / 1051).

Only if that walk finished cleanly, or the pod’s Initialized condition is already true, does it go on to look at app containers (1014 / 1057). That is where the un-prefixed PodInitializing comes from: the init containers are all done, and the app container’s waiting reason is PodInitializing because it has not been created yet. In the slow-init pod above, it lasted as long as the nginx pull.

Every Init: state loops back to Init:N/M on the same container. Only a clean exit advances N, and only the last one leads to PodInitializing.

Reading init container logs

A plain kubectl logs goes to the app container, which does not exist yet:

kubectl logs init-fails
Defaulted container "app" out of: app, wait-for-db (init), migrate (init)
Error from server (BadRequest): container "app" in pod "init-fails" is waiting to start: PodInitializing

The first line lists the init containers for you. Name one with -c:

kubectl logs init-fails -c wait-for-db
connecting to db:5432
nc: bad address db

That already shows the most recent failed run, even while the pod reads Init:Error or Init:CrashLoopBackOff, because the last exited instance is still on the node. --previous asks for the run before that:

kubectl logs init-fails -c wait-for-db --previous

It worked while the pod was in Init:CrashLoopBackOff, and printed the same two lines. Asked while the pod read Init:Error, the same command returned:

unable to retrieve container logs for containerd://db8ea4e72ff82d88b27b5f38a08f3acca751195886de0fa3529e60951c7ab6bd

That is the kubelet tidying up. pruneInitContainersBeforeStart in pkg/kubelet/kuberuntime/kuberuntime_container.go (line 972 on release-1.36, 974 on release-1.37) keeps only the last exited instance of each init container and removes the rest, and it runs on every pod sync. For init containers, reach for -c <name> first; --previous often points at a container that is already gone.

kubectl logs <pod> --all-containers also includes init containers, in order, which is quicker when you do not know which one failed.

Sidecars change the count

A native sidecar is an init container with restartPolicy: Always. It is stable since Kubernetes 1.33 and starts in init order but keeps running alongside the app. A pod with a sidecar and one ordinary init container (sleep 30):

initContainers:
- name: log-shipper
  image: busybox:1.36
  restartPolicy: Always
  command: ["sh", "-c", "while true; do sleep 3600; done"]
- name: migrate
  image: busybox:1.36
  command: ["sh", "-c", "echo migrating; sleep 30; echo done"]
NAME           READY   STATUS            RESTARTS   AGE
with-sidecar   0/2     Init:0/2          0          0s
with-sidecar   1/2     Init:1/2          0          1s
with-sidecar   1/2     PodInitializing   0          31s
with-sidecar   2/2     Running           0          31s

Three things differ from a pod without one:

  • The sidecar counts in M. Init:0/2 here means one sidecar and one ordinary init container, because M is every entry in initContainers.
  • The sidecar counts as done once it has started, not once it exits, which it never will. That is how N reached 1 after one second.
  • READY includes it. The denominator is 2 with one app container: printPod adds each restartable init container to the total (959–960 / 1002–1003), and counts it as ready when it is (988 / 1031). A pod reading 1/2 with a single app container is showing you its sidecar.

The surprising part comes after initialisation. This sidecar exits 1 every 15 seconds, next to an app that runs fine:

NAME            READY   STATUS                  RESTARTS      AGE
flaky-sidecar   1/2     Init:Error              0             16s
flaky-sidecar   2/2     Running                 1 (0s ago)    16s
flaky-sidecar   1/2     Init:Error              1 (16s ago)   32s
flaky-sidecar   1/2     Init:CrashLoopBackOff   1 (12s ago)   43s
flaky-sidecar   2/2     Running                 2 (12s ago)   43s

The pod is initialised (Initialized=True) and the app never stopped, yet STATUS says Init:CrashLoopBackOff. The init walk sets Init:Error for the sidecar because it is no longer started, and the app-container pass that follows only overwrites the reason for containers that are waiting or terminated (1030 / 1073); a healthy running app leaves it alone. So on a running pod, an Init: status can be a sidecar crash-looping, not initialisation failing. It is not harmless either: with the sidecar down, the pod’s Ready condition was False, so it drops out of Service endpoints.

Sidecars also get different validation. A startupProbe on an ordinary init container is rejected outright:

The Pod "probe-on-init" is invalid: spec.initContainers[0].startupProbe: Forbidden: may not be set for init containers without restartPolicy=Always

Probes only make sense on something that keeps running, so they are allowed on sidecars and forbidden on everything else in initContainers.

Fixing it

  • Init:ErrImagePull / Init:ImagePullBackOff: read the Failed event, then fix the image reference or the pull secret. Covered in full in the ImagePullBackOff guide.
  • Init:Error / Init:CrashLoopBackOff: kubectl logs <pod> -c <init>. The usual cause is a dependency the init container waits for (a database, a DNS name, a migration lock) and gives up on. The exit code reads the same as for an app container; see CrashLoopBackOff.
  • Init:CreateContainerConfigError: the message names the missing ConfigMap or Secret. Init containers resolve references in the pod’s own namespace, like everything else.
  • Stuck at Init:N/M with no error: the init container is running. Check its logs for whatever it is waiting on, and kubectl events for a slow pull.
  • PodInitializing for a long time: init is done. The app containers are being created, and the usual wait is the app image pull.

Preventing it

  • Give waiting init containers a deadline. An init container that loops forever on “wait for db” leaves the pod at Init:0/1 with no error and no restarts. One that exits non-zero after a bounded wait gives you Init:CrashLoopBackOff and logs, which is far easier to spot.
  • Pin and mirror init images like app images. Same registry, same pull secret, a tag rather than latest.
  • Remember that sidecars show up as Init: too. On a pod that is serving traffic, Init:CrashLoopBackOff means check the sidecars before the init steps.

For how the rest of the STATUS column is derived, including why Terminating overrides almost everything, see how kubectl computes a pod’s STATUS.

KubeGlance is a native Kubernetes client for iPhone and iPad, with a full Mac app on the same core. Its pod list computes STATUS and READY the way the API server does, Init: prefixes and sidecar counts included, so a 1/2 on your phone means what it means in your terminal.

Get KubeGlance
#init-containers #sidecars #kubectl #pods #troubleshooting

The Kubernetes dashboard that fits in your pocket

KubeGlance is a native Kubernetes client for iPhone and iPad — the real dashboard, not a companion — with a full Mac app on the same core. Pods, workloads, logs and events, straight from your kubeconfig.

Download KubeGlance