K8s by Example: Init Containers

Init containers run before app containers start. They execute sequentially and must complete successfully. If any init container fails, the Pod restarts. Use for: migrations, config fetching, dependency checks.

pod-init.yaml

Init containers are defined in initContainers. They run before the main containers array starts. Each init container must exit 0 before the next one runs.

spec:
  initContainers:
    - name: migrations
      image: my-app:v1
      command: ["python", "migrate.py"]
      envFrom:
        - secretRef:
            name: db-secrets
  containers:
    - name: app
      image: my-app:v1
pod-init-sequence.yaml

Init containers run in order. Each must complete (exit 0) before the next starts. All init containers must succeed before app containers begin.

spec:
  initContainers:
    - name: step-1-migrate
      image: my-app:v1
      command: ["./migrate.sh"]
    - name: step-2-seed
      image: my-app:v1
      command: ["./seed.sh"]
    - name: step-3-validate
      image: my-app:v1
      command: ["./validate.sh"]
  containers:
    - name: app
      image: my-app:v1
pod-init-volume.yaml

Init containers share volumes with app containers. Download files, generate config, clone repos, or prepare data. emptyDir volumes persist across init and app containers.

spec:
  initContainers:
    - name: download-assets
      image: busybox
      command: ["wget", "-O", "/data/config.json",
                "https://example.com/config.json"]
      volumeMounts:
        - name: data
          mountPath: /data
  containers:
    - name: app
      image: my-app:v1
      volumeMounts:
        - name: data
          mountPath: /app/data
  volumes:
    - name: data
      emptyDir: {}
pod-wait-deps.yaml

Wait for dependencies before starting the main app. Avoids race conditions during rollouts. Better than retry logic in your application code.

initContainers:
  - name: wait-for-db
    image: busybox:1.28
    command:
      - sh
      - -c
      - |
        until nc -z postgres 5432; do
          echo "Waiting for postgres..."
          sleep 2
        done
  - name: wait-for-redis
    image: busybox:1.28
    command:
      - sh
      - -c
      - until nc -z redis 6379; do sleep 2; done
pod-init-resources.yaml

Init containers can have different resource limits than app containers. Useful when init needs more memory (migrations) but app needs less. Pod resource limits are the max of init and app containers.

spec:
  initContainers:
    - name: heavy-migration
      image: my-app:v1
      command: ["./migrate.sh"]
      resources:
        requests:
          memory: "512Mi"
          cpu: "500m"
        limits:
          memory: "1Gi"
  containers:
    - name: app
      image: my-app:v1
      resources:
        requests:
          memory: "128Mi"
terminal

Debug init containers when Pods stay in Init state. Pod stuck in Init:0/2 means 0 of 2 init containers completed. Check logs for each init container by name.

$ kubectl get pods
NAME     READY   STATUS     RESTARTS   AGE
my-app   0/1     Init:1/3   0          30s

$ kubectl logs my-app -c migrations
Running database migrations...
Migration failed: connection refused

$ kubectl describe pod my-app | grep -A20 "Init Containers"
Init Containers:
  migrations:
    State:      Terminated
    Reason:     Error
    Exit Code:  1

$ kubectl describe pod my-app | grep -A10 Events
Events:
  Type     Reason   Message
  Warning  Failed   Error: Init container failed
pod-init-timeout.yaml

Init containers don’t support probes (liveness, readiness). They run to completion and exit. Use proper exit codes and timeouts in your init scripts to prevent hanging.

initContainers:
  - name: wait-for-service
    image: busybox:1.28
    command:
      - sh
      - -c
      - |
        timeout 60 sh -c 'until nc -z api 80; do sleep 1; done'
        if [ $? -ne 0 ]; then
          echo "Timeout waiting for api"
          exit 1
        fi

Index | GitHub | Use arrow keys to navigate |