What are init containers, and how do they differ from regular containers in a Pod?
Quick Answer
Init containers run and must complete successfully, one at a time in order, *before* any of a Pod's regular (main) containers start. They're used for setup work that must finish before the application starts — waiting for a dependency to become available, running a one-time setup script, or populating a shared volume with data the main container needs — and unlike regular containers, they aren't expected to run for the Pod's whole lifetime.
Detailed Answer
Anatomy
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']
- name: run-migrations
image: myapp-migrator:1.0
command: ['./migrate.sh']
containers:
- name: app
image: myapp:1.0
Execution order: wait-for-db runs to completion first, then run-migrations runs to completion, and only after both succeed does the main app container start. If any init container fails, Kubernetes retries that init container (subject to the Pod's restartPolicy) — the main containers never start until every init container has exited successfully.
Key differences from regular containers
| Init containers | Regular (main) containers | |
|---|---|---|
| Execution order | Sequential, one at a time, in the order listed | Started together, run concurrently |
| Expected to finish | Yes — must exit successfully to proceed | No — expected to keep running |
| Run before main containers start | Yes, always | N/A |
| Restart on crash | Retried per Pod's restartPolicy, blocking Pod startup until success | Restarted per restartPolicy, but the Pod is already considered started |
| Can use different images/tools than the app | Yes — commonly a minimal utility image | Usually the application's own image |
Common use cases
- Waiting for a dependency — blocking until a database or another service is reachable before starting the application, avoiding a crash-loop of the main container repeatedly failing to connect during startup.
- One-time setup — running database migrations, downloading/generating a configuration file, or performing a one-time registration step.
- Populating a shared volume — an init container can write files (e.g., cloning a git repo, or fetching static assets) into a volume that the main container then mounts and serves, keeping tooling needed only for setup (like
git) out of the main application's image entirely.
initContainers:
- name: fetch-content
image: alpine/git
command: ['git', 'clone', 'https://github.com/example/content.git', '/content']
volumeMounts:
- name: content-volume
mountPath: /content
containers:
- name: web
image: nginx
volumeMounts:
- name: content-volume
mountPath: /usr/share/nginx/html
volumes:
- name: content-volume
emptyDir: {}
Why not just put this logic in the main container's entrypoint script
You could — but separating it into an init container has real advantages: it keeps the main application image free of setup-only tooling (smaller image, smaller attack surface), gives setup failures a distinct, separately-visible status in kubectl get pods (an init container failure shows as Init:Error or Init:CrashLoopBackOff, immediately telling you the problem is in setup, not the application itself), and cleanly separates "must happen once, in order, before anything else" logic from the application's own ongoing run loop.