A Kubernetes pod can define containers that run before the main container, to full completion, before it starts. This mechanism, init containers, solves a simple structural problem: some preparation steps need to be finished, not just started, before the application can safely run.

Sequential execution, strictly in order

A pod’s init containers run one at a time, in declared order, each needing to finish successfully before the next starts. The main container only starts once every init container has completed: a failure in any one of them leaves the pod waiting, never launching the application container on incomplete preparation.

spec:
  initContainers:
    - name: wait-for-db
      image: busybox
      command: ['sh', '-c', 'until nc -z postgres 5432; do sleep 2; done']
    - name: run-migrations
      image: myapp-migrate
      command: ['./migrate.sh']
  containers:
    - name: app
      image: myapp

What this solves that probes don’t

A readiness probe checks that an already-started container is ready to receive traffic, but the main container has already started by then, potentially before its dependency is ready (a database not yet reachable, a schema migration not yet applied). An init container moves that wait before the application container’s own startup: the application never sees an unprepared intermediate state, because it simply doesn’t start before then.

The classic trap: an init container that never finishes

An init container looping in a wait with no proper exit mechanism (until nc -z ... ; done, with no timeout ever) blocks the pod indefinitely if the expected dependency never becomes available, without ever triggering a visible CrashLoopBackOff on the main container’s side, since it never started. The pod stays in Init:0/2, a state that gets diagnosed differently from a classic CrashLoopBackOff: kubectl describe pod is still the first command, but the logs to check belong to the init container, not the main one.

# A pod stuck in Init:X/Y never reached
# the main container, the logs to read live elsewhere
kubectl logs my-pod -c wait-for-db

The native sidecar: an init container that never finishes, on purpose

Since Kubernetes 1.29, an init container can declare restartPolicy: Always, which fundamentally changes its behavior: it starts before the main container like a classic init container, but keeps running alongside it once it launches, automatically restarting if it dies, exactly like a container in the main pod.

spec:
  initContainers:
    - name: log-shipper
      image: fluent-bit
      restartPolicy: Always   # becomes a native sidecar, runs continuously
  containers:
    - name: app
      image: myapp

This native sidecar guarantees a startup order that a webhook-injected service mesh sidecar (see the Istio vs Linkerd article) didn’t natively guarantee before this feature: the log-shipping or network proxy sidecar starts and becomes ready before the application container, eliminating a class of startup bugs where the application tries to use a sidecar that isn’t operational yet.

Takeaway

Init containers run sequentially to full completion before the main container, moving a dependency wait ahead of application startup rather than handling it via a probe afterward. An init container looping with no proper exit blocks the pod in Init:X/Y, a state to diagnose via its own logs, distinct from a classic CrashLoopBackOff. The native sidecar (restartPolicy: Always, Kubernetes 1.29+) guarantees startup ordering for a container that needs to run continuously, a primitive that matters the moment a Kubernetes migration needs to make a multi-container pod’s startup order reliable.