What is an Init Container in Kubernetes? Explained Simply
Init containers run before your main app container starts. Here's what they are, when to use them, and real examples for database migrations, config setup, and more.
An init container is a special container that runs before your main application container starts. Once it completes successfully, Kubernetes starts the main container. If it fails, the pod restarts.
Think of it as a setup step that must pass before your app is allowed to run.
Why Do They Exist?
Some tasks need to happen before your app starts:
- Database migrations — run schema changes before the app tries to use the DB
- Wait for dependencies — don't start until the database/API is ready
- Download config files — fetch secrets or config from an external source
- Set permissions — change file ownership on a shared volume before the app reads it
You could do all of this in your main container's entrypoint script, but that mixes concerns and makes your container image messy. Init containers keep this separate and clean.
Basic Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
template:
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z postgres-service 5432; do echo waiting for postgres; sleep 2; done']
containers:
- name: my-app
image: my-app:v1.0.0
ports:
- containerPort: 8080The wait-for-db init container keeps trying until it can connect to port 5432 on postgres-service. Only then does my-app start. No more "connection refused" errors on startup.
Init Container vs Sidecar Container
| Init Container | Sidecar Container | |
|---|---|---|
| When it runs | Before main container | Alongside main container |
| Lifecycle | Runs once, then exits | Runs for pod's lifetime |
| Must succeed | Yes (pod restarts if it fails) | No (independent lifecycle) |
| Typical use | Setup tasks | Logging, proxies, monitoring agents |
Real Example: Database Migration with Flyway
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api
spec:
template:
spec:
initContainers:
- name: run-migrations
image: flyway/flyway:10
args:
- migrate
env:
- name: FLYWAY_URL
value: "jdbc:postgresql://postgres-service:5432/mydb"
- name: FLYWAY_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: FLYWAY_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: migrations
mountPath: /flyway/sql
containers:
- name: api
image: backend-api:v2.0.0
# starts only after migrations succeed
volumes:
- name: migrations
configMap:
name: db-migrationsThe Flyway init container runs all pending SQL migrations. If a migration fails, the pod restarts and tries again. Your application never starts with an outdated schema.
Real Example: Wait for a Service to Be Ready
initContainers:
- name: wait-for-redis
image: redis:7-alpine
command:
- sh
- -c
- |
until redis-cli -h redis-service -p 6379 ping | grep -q PONG; do
echo "Waiting for Redis..."
sleep 2
done
echo "Redis is ready!"Real Example: Copy Static Assets to Shared Volume
spec:
initContainers:
- name: copy-assets
image: my-frontend:v1.0.0
command: ['cp', '-r', '/app/dist/.', '/shared-assets/']
volumeMounts:
- name: shared-assets
mountPath: /shared-assets
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: shared-assets
mountPath: /usr/share/nginx/html
volumes:
- name: shared-assets
emptyDir: {}The init container copies built frontend files to an emptyDir volume. The nginx container then serves them. This way you don't need nginx + node in the same image.
Multiple Init Containers
You can have multiple init containers. They run in order — each must complete before the next starts:
initContainers:
- name: step-1-wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done']
- name: step-2-run-migrations
image: flyway/flyway:10
args: [migrate]
env:
- name: FLYWAY_URL
value: "jdbc:postgresql://db:5432/myapp"
- name: step-3-seed-cache
image: redis:7-alpine
command: ['sh', '-c', 'redis-cli -h redis SET warmup_done 1']They run in this exact order: wait → migrate → seed. If step 2 fails, step 3 never runs.
Checking Init Container Status
# See pod status including init containers
kubectl get pod my-app-pod -o wide
# Detailed view
kubectl describe pod my-app-podYou'll see output like:
Init Containers:
wait-for-db:
State: Terminated
Reason: Completed
Exit Code: 0
run-migrations:
State: Running
Get logs from an init container:
kubectl logs my-app-pod -c wait-for-db
kubectl logs my-app-pod -c run-migrationsIf the init container fails, the pod shows Init:CrashLoopBackOff or Init:Error. Check logs to see why.
Key Rules
- Init containers must complete with exit code 0 (success) before the main container starts
- If an init container fails, Kubernetes restarts the entire pod (respecting
restartPolicy) - Init containers don't have readiness probes — they must run to completion
- Init containers share volumes and network namespace with the main container
- Multiple init containers run sequentially, not in parallel
Init containers are one of the cleanest patterns in Kubernetes for handling startup dependencies. Use them instead of putting sleep loops in your application entrypoint.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
Build a Kubernetes Cluster with kubeadm from Scratch (2026)
Step-by-step guide to building a real multi-node Kubernetes cluster using kubeadm — no managed services, no shortcuts.
How to Build a DevOps Home Lab for Free in 2026
You don't need expensive hardware to practice DevOps. Here's how to build a complete home lab with Kubernetes, CI/CD, and monitoring using free tools and cloud free tiers.
How to Crack the CKA Exam in 2026: Study Plan, Resources, and Tips
Complete CKA exam prep guide for 2026 — what to study, how to practice, which resources actually help, and tips to pass on the first attempt.