KubernetesXI · Init Containers and SidecarsInit containers and sidecars
Init containers — sequential setup before the main container
What you'll learn
- Configure initContainers in a Pod spec
- Identify the right use cases for init containers
- Reason about init container ordering and resource sharing
- Diagnose init container failures
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Init containers are a separate list in the Pod spec that run sequentially before the main containers. They are the right tool for setup that must complete before the workload starts: waiting for dependencies, running migrations, fetching configuration, generating secrets. This lesson covers the init container model, the right use cases, and the production discipline.
The init container model
spec:
initContainers:
- name: init-db
image: busybox:1.36
command: ["sh", "-c", "until nc -z db 5432; do sleep 1; done"]
- name: init-migration
image: migrate/migrate:4
command: ["migrate", "-path=/migrations", "-database=...", "up"]
containers:
- name: app
image: app:1.0.0
Init containers run sequentially:
init-dbstarts, runs to completion.- If
init-dbexits 0:init-migrationstarts. - If
init-migrationexits 0: the main containers start. - If any init container fails: the Pod stays in
PendingwithInitialized: False, and the kubelet retries the failed init container.
sequenceDiagram
participant API as API server
participant K as Kubelet
participant I1 as init-db
participant I2 as init-migration
participant App as app
API->>K: Pod bound
K->>I1: start init-db
I1-->>K: exit 0
K->>I2: start init-migration
I2-->>K: exit 0
K->>App: start app (containers)
App-->>K: running
Init containers share the Pod’s volumes and network namespace
with the main containers. They can write files to emptyDir
volumes that the main containers read. They can wait for
network services that the main containers will use.
Common use cases
Waiting for a dependency:
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z db 5432; do
echo "Waiting for db..."
sleep 2
done
The init container polls the database port until it’s reachable. The main container can then start with the guarantee that the database is up.
Running migrations:
initContainers:
- name: migrate
image: migrate/migrate:4
command: ["migrate", "-path=/migrations", "-database=postgres://...", "up"]
volumeMounts:
- name: migrations
mountPath: /migrations
volumes:
- name: migrations
configMap:
name: db-migrations
The init container runs the migration tool against the database. The main container can then start with the schema up to date.
Generating configuration:
initContainers:
- name: render-config
image: alpine:3.19
command:
- sh
- -c
- |
envsubst < /template/config.tpl > /config/app.conf
volumeMounts:
- name: template
mountPath: /template
- name: config
mountPath: /config
volumes:
- name: template
configMap:
name: app-config-template
- name: config
emptyDir: {}
The init container renders a configuration template with environment variables and writes it to a shared volume. The main container reads the rendered configuration.
Fetching secrets:
initContainers:
- name: fetch-secret
image: vault:1.15
command:
- sh
- -c
- |
vault read -format=json secret/data/app > /secret/app.json
volumeMounts:
- name: secret
mountPath: /secret
volumes:
- name: secret
emptyDir: {}
The init container fetches a secret from Vault and writes it to a shared volume. The main container reads the secret.
Init container resources
initContainers:
- name: migrate
image: migrate/migrate:4
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Init containers have separate resource requests and limits from the main containers. This is important because:
- The scheduler uses the sum of all container (init + main) requests to decide if a node has capacity.
- Init containers run briefly and may need different resources than the long-running main container.
- A migration may need 1 GB of memory briefly; the main container may need only 256 MB sustained.
The “effective” requests for scheduling are the largest of any init container and the largest of any main container:
flowchart LR
Init1[init container 1<br/>requests] --> Sum[Sum per resource]
Init2[init container 2<br/>requests] --> Sum
Main[main container<br/>requests] --> Sum
Sum --> Sched[Used for scheduling]
For a Pod with:
- init-1: requests cpu=100m, memory=256Mi
- init-2: requests cpu=200m, memory=512Mi
- main: requests cpu=500m, memory=1Gi
The scheduler uses:
- cpu: max(100m, 200m) + 500m = 700m (init runs sequentially, so the largest init is the per-Pod init request; main runs concurrently with no init)
- memory: same logic
Actually, the formula is more nuanced. The kubelet calculates the effective requests as the max of (sum of init requests) and (sum of all requests). For sequential init containers:
- The sum of init requests at any moment is the largest init request (since they run one at a time).
- The main containers’ requests add on top.
- So effective requests = max init requests + main requests.
The exact formula is documented in the Pod resource specification. For practical purposes:
- Resource requests for init containers are typically smaller than main containers (they run briefly).
- If an init container needs significant resources, consider its impact on scheduling.
Init container vs readiness probe
A common question: should I wait for a dependency with an init container or a readiness probe?
# Init container: blocks Pod start until dependency is up
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ["sh", "-c", "until nc -z db 5432; do sleep 2; done"]
vs
# Readiness probe: Pod starts, but is not Ready until dependency is up
readinessProbe:
exec:
command: ["sh", "-c", "nc -z db 5432"]
The difference:
- Init container: blocks the Pod from starting main
containers. The Pod is
Pending(notRunning) until the init succeeds. - Readiness probe: the Pod starts main containers, but
is not Ready until the probe succeeds. The Pod is
Runningbut not in Endpoints.
Use init container when the main container cannot function without the dependency (e.g., a migration must run before the app starts). Use readiness probe when the main container can start but should not receive traffic until the dependency is up.
Diagnosing init container failures
A Pod stuck in Pending with Initialized: False:
kubectl describe pod web-7c8
# Events:
# ... reason: Failed message: Error: failed to start container "init-migration": ...
Or:
kubectl get pod web-7c8 -o jsonpath='{.status.initContainerStatuses}' | jq
Output:
[
{
"name": "init-migration",
"state": {
"terminated": {
"exitCode": 1,
"reason": "Error",
"finishedAt": "..."
}
},
"lastState": {},
"ready": false,
"restartCount": 5
}
]
The init container exited with code 1; the kubelet retried 5 times. The diagnosis:
kubectl logs web-7c8 -c init-migration --previous
Common failure modes:
- Dependency unreachable: the wait-for-db init container is stuck; check the database’s status.
- Migration script error: the migration tool failed; check the logs.
- ConfigMap not found: the init container mounts a
ConfigMap that does not exist. Check
kubectl get configmap. - Image pull failure: the init container’s image is missing or the registry is unreachable.
Production patterns
Long-running init with timeout:
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
timeout 300 sh -c 'until nc -z db 5432; do sleep 2; done' || exit 1
The init container has a 300s timeout. If the database is not up in 5 minutes, the init fails and the Pod retries.
Conditional init with success/failure semantics:
initContainers:
- name: check-config
image: busybox:1.36
command:
- sh
- -c
- |
if [ -f /config/ready ]; then
echo "Config ready"
exit 0
else
echo "Config missing"
exit 1
fi
volumeMounts:
- name: config
mountPath: /config
The init succeeds if the config file exists, fails otherwise.
Cross-course references
- The Linux course part
XXII-Linux-NetTroubleshootcovers dependency waiting; init containers are the cluster-level equivalent. - The Ansible course part
XX-Ansible-SSHcovers setup tasks; init containers are the cluster-level equivalent. - The Docker course part
XXIX-Docker-Buildcovers image layers; init containers run additional images sequentially.
Quiz
Knowledge check · 4 questions
Q1. How do init containers run relative to the main containers and to each other?
Q2. An init container can be restarted while it is running if it hits a transient error.
Q3. A team's init container runs a database migration. The migration succeeds on the first Pod, but subsequent Pods fail because the migration tool tries to apply migrations that already ran. Diagnose.
Deployment has 3 replicas. The init container runs `migrate -path=/migrations up`. The first Pod's init succeeds; the migration runs. The second and third Pods' init containers try to run the same migration and fail with 'migration already applied' (exit code 1).
Q4. When is the right tool an init container for waiting on a dependency, and when is the right tool a readiness probe?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Use init containers for blocking setup. Migrations, config generation, dependency waiting.
- Make init containers idempotent. They may run multiple times due to retries; non-idempotent init causes Pod failures.
- Size init container resources separately from main containers. They run briefly and may need different resources.
- Add timeouts to wait-for-dependency init containers. An infinite wait deadlocks the Pod.
- Diagnose init failures with
kubectl logs -c <name> --previous. The init’s logs are the diagnostic key.