Your main application container sometimes needs things to be ready before it starts — a database must be reachable, a config file must be downloaded, migrations must run. If the main container starts before these are ready, it crashes or behaves incorrectly.
Init Containers solve this. They run to completion before your main container starts. If an init container fails, Kubernetes retries it until it succeeds — the main container never starts until all init containers pass.
Pod starts
|
Init Container 1 runs -> must complete successfully
|
Init Container 2 runs -> must complete successfully
|
Main container starts -> only now
Init containers run sequentially, not in parallel. Each one must finish before the next begins. Only after all init containers complete does the main app container start.
This is different from regular containers — init containers are not long-running. They run once, finish their job, and exit.
Wait for a service to be ready
-> init container loops until DB is reachable, then exits
-> main app starts knowing DB is available
Run database migrations
-> init container runs migrate command and exits
-> main app starts on a fully migrated schema
Download config or secrets from external source
-> init container fetches config file into a shared volume
-> main app reads the file on startup
Set permissions or prepare directories
-> init container creates folders, sets ownership
-> main app has the correct file structure ready
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting for db; sleep 2; done']
# loops every 2 seconds until port 5432 on db-service is open
# exits successfully when DB is reachable
- name: run-migrations
image: my-app:latest
command: ['python', 'manage.py', 'migrate']
# runs DB migrations and exits
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080
# starts only after both init containers above have completed
Init containers can write files to a shared volume that the main container reads. This is how you pass downloaded configs or prepared files.
spec:
volumes:
- name: shared-data
emptyDir: {} # temporary volume shared between containers
initContainers:
- name: download-config
image: busybox
command: ['sh', '-c', 'wget -O /shared/config.json <http://config-server/config>']
volumeMounts:
- name: shared-data
mountPath: /shared
containers:
- name: my-app
image: my-app:latest
volumeMounts:
- name: shared-data
mountPath: /app/config # reads config.json written by init container
| Init Container | Regular Container | |
|---|---|---|
| Runs | Once, before main container | Continuously |
| Must complete | Yes — exits with code 0 | No — runs until stopped |
| On failure | Pod retries init container | Pod restarts based on restartPolicy |
| Parallel | No — sequential | Yes — all run together |
| Use for | Setup, wait, migrate | Your actual application |