| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Applies to | Any host with kubectl configured; any Kubernetes cluster |
| Cert prep | CKAD |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Scope | Regular init-container YAML, sequential ordering, shared volumes, status inspection, failure and restart behaviour, idempotency, and resource scheduling. Does not cover native sidecar configuration beyond a brief qualification, probe tuning, Jobs, ConfigMap or Secret depth, PV provisioning, or SecurityContext depth. |
| Related guides | Container command, args, and environment |
This article covers regular init containers that run to completion. Kubernetes also defines native sidecars under spec.initContainers using container-level restartPolicy: Always; those continue running with the application and are covered in the Kubernetes sidecar pattern guide.
This walkthrough uses one Pod named web in the init-lab namespace. Two regular init containers prepare a configuration file on a shared emptyDir volume; the application container reads that file after initialization completes.
What Is a Kubernetes Init Container?
A regular init container performs setup work before application containers in the same Pod start.
- Regular init containers are defined under
spec.initContainers - They run sequentially in the order listed
- Each regular init container must complete successfully before the next one starts
- Application containers start only after all regular init containers succeed
For Pod phases, conditions, and the wider lifecycle, see Kubernetes Pods and Pod Lifecycle.
Regular init containers versus application containers
| Regular init container | Application container |
|---|---|
| Runs before application containers | Runs after initialization completes |
| Must normally complete successfully | Commonly remains running |
| Regular init containers execute sequentially | Application containers can run concurrently |
| Used for preparation tasks | Runs the main workload |
Does not support lifecycle, livenessProbe, readinessProbe, or startupProbe |
Supports lifecycle handlers and probes |
Native sidecars are the exception and can use probes. Probe configuration detail belongs in the Kubernetes health probes and sidecar guides.
Regular init containers versus native sidecars
Kubernetes implements native sidecars as entries under spec.initContainers with container-level restartPolicy: Always. Unlike regular init containers, native sidecars do not run to completion before the application starts. They start in init order and remain running alongside application containers. This behaviour is stable and enabled by default in current Kubernetes.
- Use a regular init container for one-time setup: configuration, downloads, permission fixes, or in-Pod dependency checks
- Use a native sidecar for continuous helper work during the Pod lifetime, such as log shipping or proxying
For multi-container Pod YAML, native sidecar lifecycle, probes, startup ordering, and shutdown behaviour, see Kubernetes sidecar pattern.
Create a Pod with Init Containers
Save this manifest as web-init-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: init-lab
spec:
initContainers:
- name: write-config
image: busybox:1.36
command:
- sh
- -c
- |
sleep 5
echo "app_mode=production" > /work/config.env
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> /work/config.env
echo "write-config done"
volumeMounts:
- name: workdir
mountPath: /work
- name: verify-config
image: busybox:1.36
command:
- sh
- -c
- |
sleep 5
test -f /work/config.env
echo "config verified"
volumeMounts:
- name: workdir
mountPath: /work
containers:
- name: app
image: busybox:1.36
command: ['sh', '-c', 'echo "Application starting" && cat /work/config.env && sleep 3600']
volumeMounts:
- name: workdir
mountPath: /work
volumes:
- name: workdir
emptyDir: {}The write-config regular init container generates a file on the shared volume. The verify-config init container confirms it exists before app starts.
Create the namespace, then apply the Pod:
kubectl create namespace init-labSample output:
namespace/init-lab createdkubectl apply -f web-init-pod.yamlSample output:
pod/web createdWatch initialization progress:
kubectl get pod web -n init-lab --watchYou should see progression similar to:
web 0/1 Init:0/2 0 1s
web 0/1 Init:1/2 0 6s
web 1/1 Running 0 12sGenerated timing will vary. Press Ctrl+C when the Pod reaches Running.
Read init container logs:
kubectl logs web -n init-lab -c write-configSample output:
write-config donekubectl logs web -n init-lab -c verify-configSample output:
config verifiedThe application log confirms it read the prepared file:
kubectl logs web -n init-lab -c appSample output:
Application starting
app_mode=production
timestamp=2026-07-26T06:18:24ZUnderstand Init Container Behaviour
Sequential ordering
This Pod runs two regular init containers before the application:
write-configcreates the required data on the shared volumeverify-configverifies the file existsappstarts only after both succeed
Regular init containers run one at a time. Kubernetes does not start verify-config until write-config exits with code 0. Reordering the list under spec.initContainers changes the initialization sequence.
write-config → verify-config → appSharing data through a volume
Containers in the same Pod do not share files automatically. Both must mount the same volume. This example uses emptyDir, which survives individual container restarts but is removed when the Pod is deleted. For volume types and persistence concepts, see Kubernetes volumes.
| Component | Mount | Action |
|---|---|---|
write-config |
workdir at /work |
Writes config.env |
verify-config |
workdir at /work |
Confirms the file exists |
app |
workdir at /work |
Reads the prepared file |
The mount paths can differ between containers as long as they reference the same volume name.
Resource requests and scheduling
Regular init containers run sequentially, so Kubernetes does not add all their requests together. For each resource, it compares:
- the highest request from any regular init container
- the combined request of the application containers
The larger value becomes the effective Pod request used for scheduling. A short-lived init container with a large CPU or memory request can therefore prevent the Pod from being scheduled on smaller nodes even though that resource is needed only during initialization.
For requests, limits, and QoS detail, see Kubernetes resource requests and limits.
Re-execution and idempotency
Write init-container commands so they are idempotent. Completed regular init containers do not rerun merely because an application container restarts, but the init sequence can run again when the Pod itself or its sandbox is restarted. Deleting and recreating the Pod always runs the initialization sequence again.
The official documentation recommends accounting for possible re-execution and lists Pod-infrastructure restart scenarios that can rerun initialization. A safe file-generation pattern is to write to a temporary file and rename it only after successful completion.
When a regular init container fails, application containers do not start. In these examples, the regular init containers do not define a container-level restart policy, so the kubelet follows the Pod restartPolicy. On Kubernetes versions with ContainerRestartRules enabled, a regular init container can define its own restartPolicy and optional restartPolicyRules, overriding the Pod-level behavior.
Common Init Container Patterns
| Pattern | Purpose |
|---|---|
| Generate configuration | Create runtime configuration before startup |
| Copy files | Populate a shared volume from a utility image |
| Wait for a dependency | Delay startup until a required endpoint responds |
| Prepare permissions | Adjust shared-volume ownership or mode |
| Download startup data | Fetch assets without adding tools to the application image |
The lab write-config init container demonstrates generate configuration. A dependency wait typically uses a short retry loop such as until wget -qO- http://dependency; do sleep 2; done to block Pod startup until an endpoint responds. That controls order inside the Pod; the application should still handle transient failures after startup.
Demonstrate Failure and Restart Behaviour
When a regular init container fails, application containers do not start.
Failure with Always or OnFailure
Save this manifest as fail-init.yaml:
apiVersion: v1
kind: Pod
metadata:
name: fail-init
namespace: init-lab
spec:
restartPolicy: Always
initContainers:
- name: bad-init
image: busybox:1.36
command:
- sh
- -c
- |
echo "initialization failed"
exit 1
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]Apply it and watch the Pod:
kubectl apply -f fail-init.yamlSample output:
pod/fail-init createdkubectl get pod fail-init -n init-lab --watchPress Ctrl+C after you observe Init:CrashLoopBackOff. Otherwise, that command continues watching indefinitely.
Sample output:
NAME READY STATUS RESTARTS AGE
fail-init 0/1 Init:CrashLoopBackOff 2 25sFor regular init containers with Pod-level restartPolicy: Always, Kubernetes behaves like OnFailure for initialization: a successful init container is not restarted, but a failed one is retried with increasing delay. Status may show Init:Error briefly between retries, then Init:CrashLoopBackOff while the kubelet backs off.
Failure with Never
With restartPolicy: Never, Kubernetes does not retry a non-zero init exit. kubectl get pods may display Init:Error, while the Pod's API phase becomes Failed. Init:Error is a kubectl STATUS value, not a Pod phase.
Save this manifest as fail-init-never.yaml:
apiVersion: v1
kind: Pod
metadata:
name: fail-init-never
namespace: init-lab
spec:
restartPolicy: Never
initContainers:
- name: bad-init
image: busybox:1.36
command:
- sh
- -c
- |
echo "initialization failed"
exit 1
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]kubectl apply -f fail-init-never.yamlCheck both the displayed status and the API phase:
kubectl get pod fail-init-never -n init-labSample output:
NAME READY STATUS RESTARTS AGE
fail-init-never 0/1 Init:Error 0 8skubectl get pod fail-init-never -n init-lab -o jsonpath='{.status.phase}{"\n"}'Sample output:
FailedJob-specific restart behaviour is outside this article.
Inspect Init Container Status and Logs
Check current Pod status:
kubectl get pod web -n init-labkubectl get pods may show initialization progress such as:
| STATUS | Meaning |
|---|---|
Init:0/2 |
Zero of two regular init containers have completed |
Init:1/2 |
One of two regular init containers has completed |
Init:Error |
A regular init container failed |
Init:CrashLoopBackOff |
A failing regular init container is being retried with backoff |
These are displayed status reasons from the kubelet, not separate Pod phases. Use kubectl describe pod for init container state, exit codes, and Events.
Inspect init-container state directly from the API:
kubectl get pod web -n init-lab -o jsonpath='{range .status.initContainerStatuses[*]}{.name}{"\t"}{.state.terminated.reason}{"\t"}{.restartCount}{"\n"}{end}'Sample output:
write-config Completed 0
verify-config Completed 0Kubernetes stores init-container state separately under .status.initContainerStatuses.
Read logs from a specific init container:
kubectl logs web -n init-lab -c write-configAfter a failed regular init container restarts, read logs from the previous attempt when available:
kubectl logs fail-init -n init-lab -c bad-init --previousSample output:
initialization failedUse --previous only for a restarted init container when earlier output is still available.
Change a Regular Init Container Safely
Most fields of spec.initContainers are immutable on an existing Pod. For a regular init container, Kubernetes permits updating the image field, but the change does not restart the Pod or rerun an init container that has already completed. Native sidecar image updates behave differently and are covered in the Kubernetes sidecar pattern guide. To change command, arguments, environment, mounts, or to guarantee that new initialization code runs, recreate the bare Pod. For controller-managed Pods, update the Pod template so the controller creates replacement Pods.
When the Pod is owned by a Deployment, updating the Pod template creates replacement Pods that run the updated initialization sequence. See Deployments and rolling updates for controller-based rollout.
Troubleshoot Common Init Container Problems
Start with describe and logs for the stuck or failing init container:
kubectl describe pod <pod> -n <namespace>
kubectl logs <pod> -n <namespace> -c <init-container>Include --previous only for a restarted init container.
| Symptom | Likely cause | First check |
|---|---|---|
Stuck at Init:0/N |
First init failing or waiting | Logs and describe for first init |
Init:CrashLoopBackOff |
Non-zero exit from init | Init container logs |
| App missing prepared file | Volume or path mismatch | Volume name and mountPath in both containers |
| Wrong setup order | Init list order | spec.initContainers sequence |
| App never starts | Init not completing | All init exit codes in describe |
What's Next
- Kubernetes Sidecar and Multi-Container Patterns with Examples
- Kubernetes Jobs with Examples
- Kubernetes CronJobs with Examples
References
- Init Containers
- Debug Init Containers
- Sidecar Containers
- Resource Management for Pods and Containers
- Pod Lifecycle
- Volumes
Summary
Regular Kubernetes init containers run sequential setup work before application containers start. Define them under spec.initContainers, mount a shared volume such as emptyDir, and let the first init container prepare files the application reads later. The lab Pod moved from Init:0/2 to Running only after write-config and verify-config both exited with code 0.
Regular init containers run one at a time in list order, and a failure blocks every application container. Native sidecars under the same field with restartPolicy: Always are a separate pattern covered in the sidecar guide. Check Init:N/M status, .status.initContainerStatuses, init container logs, and describe output before debugging the main application. When init containers live inside a Deployment template, changing the init spec rolls out new Pods that rerun the full initialization chain.

