What are init containers, and how do they differ from regular containers in a Pod?

5 minintermediateinit-containerspodspod-lifecycle

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 containersRegular (main) containers
Execution orderSequential, one at a time, in the order listedStarted together, run concurrently
Expected to finishYes — must exit successfully to proceedNo — expected to keep running
Run before main containers startYes, alwaysN/A
Restart on crashRetried per Pod's restartPolicy, blocking Pod startup until successRestarted per restartPolicy, but the Pod is already considered started
Can use different images/tools than the appYes — commonly a minimal utility imageUsually 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.