Kubernetes Init Containers Patterns and Examples
Use Kubernetes init containers for pod initialization. Wait for dependencies, clone Git repos, setup configuration, database migrations, certificate
π‘ Quick Answer: Init containers run before app containers, executing sequentially to completion. Use them to: wait for dependencies (
nslookupuntil service resolves), run database migrations, clone code, fetch secrets, or fix file permissions. They share volumes with app containers but have separate images and resource limits.
The Problem
- Application crashes on startup because the database isnβt ready yet
- Need to run migrations before the app starts
- Need to clone a Git repo or fetch config before the main container uses it
- File permissions on mounted volumes are wrong for the app user
- Want to separate initialization concerns from the application image
The Solution
Wait for Dependency
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
spec:
initContainers:
# Wait until database service is resolvable
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nslookup postgres.production.svc.cluster.local; do
echo "Waiting for database..."
sleep 2
done
echo "Database is ready!"
# Wait until database accepts connections
- name: wait-for-db-ready
image: postgres:16-alpine
command:
- sh
- -c
- |
until pg_isready -h postgres.production -p 5432; do
echo "Database not accepting connections..."
sleep 2
done
containers:
- name: api
image: registry.example.com/api:v2Database Migration
spec:
initContainers:
- name: migrate
image: registry.example.com/api:v2 # Same image as app
command: ["./migrate", "--up"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
containers:
- name: api
image: registry.example.com/api:v2Clone Git Repository
spec:
initContainers:
- name: git-clone
image: alpine/git:2.43
command:
- git
- clone
- --single-branch
- --branch=main
- --depth=1
- https://github.com/example/config-repo.git
- /config
volumeMounts:
- name: config-volume
mountPath: /config
containers:
- name: app
image: registry.example.com/app:v1
volumeMounts:
- name: config-volume
mountPath: /app/config
readOnly: true
volumes:
- name: config-volume
emptyDir: {}Fix Volume Permissions
spec:
initContainers:
- name: fix-permissions
image: busybox:1.36
command: ["sh", "-c", "chown -R 1000:1000 /data && chmod 750 /data"]
securityContext:
runAsUser: 0 # Root needed to chown
volumeMounts:
- name: data-volume
mountPath: /data
containers:
- name: app
image: registry.example.com/app:v1
securityContext:
runAsUser: 1000
volumeMounts:
- name: data-volume
mountPath: /data
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: app-dataFetch Certificates/Secrets
spec:
initContainers:
- name: fetch-certs
image: bitnami/kubectl:1.31
command:
- sh
- -c
- |
kubectl get secret tls-cert -n production \
-o jsonpath='{.data.tls\.crt}' | base64 -d > /certs/tls.crt
kubectl get secret tls-cert -n production \
-o jsonpath='{.data.tls\.key}' | base64 -d > /certs/tls.key
chmod 400 /certs/tls.key
volumeMounts:
- name: certs
mountPath: /certs
containers:
- name: nginx
image: nginx:1.25
volumeMounts:
- name: certs
mountPath: /etc/nginx/ssl
readOnly: true
volumes:
- name: certs
emptyDir:
medium: Memory # tmpfs β never written to diskMultiple Init Containers (Sequential)
spec:
initContainers:
# Runs first
- name: wait-for-cache
image: busybox:1.36
command: ["sh", "-c", "until nc -z redis.production 6379; do sleep 1; done"]
# Runs second (after first completes)
- name: wait-for-db
image: busybox:1.36
command: ["sh", "-c", "until nc -z postgres.production 5432; do sleep 1; done"]
# Runs third
- name: migrate
image: registry.example.com/api:v2
command: ["./migrate", "--up"]
# Runs fourth
- name: seed-cache
image: registry.example.com/api:v2
command: ["./seed-cache"]
# Only starts after ALL init containers succeed
containers:
- name: api
image: registry.example.com/api:v2Download and Extract
spec:
initContainers:
- name: download-model
image: curlimages/curl:8.5.0
command:
- sh
- -c
- |
curl -L -o /models/model.bin \
"https://models.example.com/llm/v1/model.bin"
echo "Model downloaded: $(ls -lh /models/model.bin)"
volumeMounts:
- name: model-volume
mountPath: /models
containers:
- name: inference
image: registry.example.com/inference:v1
volumeMounts:
- name: model-volume
mountPath: /models
readOnly: true
volumes:
- name: model-volume
emptyDir:
sizeLimit: 10GiCommon Issues
Init container keeps restarting (CrashLoopBackOff)
- Cause: Command failing (dependency not available yet); exit code != 0
- Fix: Add retry loop with
until; check init container logs:kubectl logs <pod> -c <init-container>
Pod stuck in βInit:0/3β forever
- Cause: First init container never completes (infinite wait, wrong hostname)
- Fix: Check:
kubectl describe pod; verify service DNS resolves; add timeout to wait loops
Init container canβt access volume
- Cause: Volume not mounted in init container spec
- Fix: Add
volumeMountsto init container (same as app container)
Init containers consume too many resources
- Cause: No resource limits set; init container doing heavy computation
- Fix: Set
resources.limits; init container resources are separate from app container
Best Practices
- Keep init containers fast β long init = long pod startup time
- Add timeouts to wait loops β donβt wait forever (exit non-zero to trigger restart)
- Use lightweight images β
busybox,alpinefor simple tasks - Share data via emptyDir β init writes, app reads (same volume)
- Set resource limits β init containers have separate resource accounting
- Log progress β
echostatements help debugging stuck init containers - Use readiness probes instead β for runtime dependencies that may flap
Key Takeaways
- Init containers run sequentially before app containers, must complete successfully
- Pod stays in
Init:X/Ystatus until all init containers finish - Common patterns: wait for deps, migrate DB, clone repos, fix permissions, fetch config
- Share data between init and app containers via shared volumes (emptyDir)
- Init containers have their own images, commands, and resource limits
- Sequential execution: second init waits for first to exit 0
kubectl logs <pod> -c <init-name>β debug specific init container

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
