Init Containers
Init containers run BEFORE the main containers in a Pod, sequentially, and must succeed before the pod is considered started. Use them for database migrations, config rendering, secret pre-fetch, network policy waits, schema setup — anything that must happen once before the app boots.
Order, restartPolicy, sidecar (1.29+), patterns
EXAMPLE
# 1) Minimal example — wait for a service before app starts
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z db.default.svc 5432; do
echo 'waiting for db…'
sleep 2
done
containers:
- name: web
image: my-web:1.2
ports: [{ containerPort: 8080 }]
# Sequential, sequential, sequential. Pod doesn't proceed until init succeeds.
# If init fails, restartPolicy decides retry behaviour (Always = restart).
# 2) Multi-step init — runs in order
spec:
initContainers:
- { name: clone-repo, image: alpine/git:2.40, command: ['git', 'clone', 'https://...', '/data'] }
- { name: install-deps, image: node:20-alpine, command: ['npm', '--prefix=/data', 'ci'] }
- { name: build, image: node:20-alpine, command: ['npm', '--prefix=/data', 'run', 'build'] }
containers:
- { name: web, image: node:20-alpine, command: ['node', '/data/dist/server.js'], volumeMounts: [{ name: data, mountPath: /data }] }
volumes:
- { name: data, emptyDir: {} }
# 3) Database migration before app boots
spec:
initContainers:
- name: migrate
image: my-app:1.2
command: ['npm', 'run', 'db:migrate']
env:
- { name: DATABASE_URL, valueFrom: { secretKeyRef: { name: app-secrets, key: DATABASE_URL } } }
containers:
- name: app
image: my-app:1.2
envFrom: [{ secretRef: { name: app-secrets } }]
# 4) Fetch secrets from Vault / cloud provider
spec:
initContainers:
- name: vault-agent-init
image: hashicorp/vault:1.16
command: ['vault', 'agent', '-config=/etc/vault/config.hcl']
volumeMounts:
- { name: secrets, mountPath: /vault/secrets }
containers:
- name: app
image: my-app:1.2
volumeMounts:
- { name: secrets, mountPath: /app/secrets, readOnly: true }
volumes:
- { name: secrets, emptyDir: {} }
# 5) Network policy ready / DNS pre-warming
spec:
initContainers:
- name: wait-net
image: alpine:3.19
command: ['sh', '-c', 'until nslookup api.example.com; do sleep 1; done']
# 6) Permissions fixing (legacy storage)
spec:
initContainers:
- name: fix-perms
image: busybox
command: ['sh', '-c', 'chown -R 1000:1000 /data && chmod -R u+rwX,g+rX /data']
volumeMounts:
- { name: data, mountPath: /data }
securityContext: { runAsUser: 0 } # only this init runs as root
containers:
- name: app
image: my-app
securityContext: { runAsUser: 1000 }
volumeMounts:
- { name: data, mountPath: /data }
# 7) Native sidecar pattern (Kubernetes 1.29+ stable)
# A sidecar is an INIT container with restartPolicy: Always — it stays running ALONGSIDE the main containers.
# Old pattern was 'just a regular container'; native sidecars get proper startup ordering.
spec:
initContainers:
- name: logging-sidecar
image: fluent/fluent-bit:3.0
restartPolicy: Always # makes it a SIDECAR (not a one-shot init)
volumeMounts:
- { name: logs, mountPath: /logs }
- name: setup
image: busybox
command: ['sh', '-c', 'mkdir -p /logs && touch /logs/app.log']
volumeMounts:
- { name: logs, mountPath: /logs }
containers:
- name: app
image: my-app:1.2
volumeMounts:
- { name: logs, mountPath: /var/log/app }
volumes:
- { name: logs, emptyDir: {} }
# Sidecar init starts FIRST, stays running. Setup init runs to completion. App runs alongside sidecar.
# 8) Resources + limits
spec:
initContainers:
- name: migrate
image: my-app:1.2
command: ['npm', 'run', 'db:migrate']
resources:
requests: { cpu: 50m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
# Init container resources can be HIGH because they run only once at startup.
# But total Pod resources sum the MAX of init + main during scheduling.
# 9) Order of operations
# 1. Volume mounts attached
# 2. initContainers run in declared order; each must Succeed
# 3. Main containers start in parallel
# 4. Readiness/liveness probes start gating after containers run
# 5. If init fails — Pod restarts according to restartPolicy (default Always)
# 10) Debugging
kubectl describe pod web # see Init events, exit codes
kubectl logs web -c wait-for-db # logs of a specific init container
kubectl logs web -c migrate --previous # last failed init
# 11) Anti-patterns
# • Long-running init container (5+ min) → slow rollouts; consider externalising the work
# • Init containers depending on the main app being ready → circular; restructure
# • Multiple init containers reading the SAME volume but expecting concurrent writes → sequential, won't work
# • Network calls in init container → handle retries; idempotency matters
# • Mounting Secret as env in init AND main with different values → confusing; use volumeMounts
# • Heavy init containers in DaemonSets → every node startup pays the cost
# 12) When to use a Job instead
# • Migrations as separate Job — runs OUTSIDE the pod lifecycle; idempotent retry; tracked in Jobs
# • Init container fine for fast, deterministic setup (waiting, fetching config)
# • Job better for one-time deployments and migrations gating a release
# 13) Common bugs
# • Init succeeded but secret not available at app start — refresh: env vs file mounts have different timing
# • restartPolicy: Always + failing init → infinite restart loop; fix the underlying error
# • Network race — DNS not ready yet → wait-for-* loop with timeout
# • forgotten 'restartPolicy: Always' on a native sidecar → behaves like a one-shot, app starts before sidecar ready
# • Init runs as root, app runs as non-root, but file ownership not adjusted → permission denied
# • Init pulls huge image → slow pod startup; use small images (busybox, alpine, distroless)
# • Image pull errors in init → ImagePullBackOff blocks the whole pod startup
# • Trying to share emptyDir contents between init containers AT THE SAME TIME — they run sequentially
Why it matters
Use init containers for sequential pre-flight tasks: wait for dependencies, run migrations, fetch secrets, fix file permissions. Each must succeed before the main containers start. Kubernetes 1.29+ makes native sidecars first-class (init container with restartPolicy: Always), so you can pair a logging or proxy sidecar with proper startup ordering.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
spec:
initContainers:
- name: migrate
image: my-app:latest
command: ["npm", "run", "migrate"]
containers: …
Try it Yourself »
Discussion
Loading…