Skip to main content
RunBook Academy

KubernetesXI · Init Containers and SidecarsInit containers and sidecars

Init containers — sequential setup before the main container

Advanced⏱ ~18 minkubectl

What you'll learn

  • Configure initContainers in a Pod spec
  • Identify the right use cases for init containers
  • Reason about init container ordering and resource sharing
  • Diagnose init container failures

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

Init containers are a separate list in the Pod spec that run sequentially before the main containers. They are the right tool for setup that must complete before the workload starts: waiting for dependencies, running migrations, fetching configuration, generating secrets. This lesson covers the init container model, the right use cases, and the production discipline.

The init container model

spec:
  initContainers:
  - name: init-db
    image: busybox:1.36
    command: ["sh", "-c", "until nc -z db 5432; do sleep 1; done"]
  - name: init-migration
    image: migrate/migrate:4
    command: ["migrate", "-path=/migrations", "-database=...", "up"]
  containers:
  - name: app
    image: app:1.0.0

Init containers run sequentially:

  1. init-db starts, runs to completion.
  2. If init-db exits 0: init-migration starts.
  3. If init-migration exits 0: the main containers start.
  4. If any init container fails: the Pod stays in Pending with Initialized: False, and the kubelet retries the failed init container.
sequenceDiagram
    participant API as API server
    participant K as Kubelet
    participant I1 as init-db
    participant I2 as init-migration
    participant App as app

    API->>K: Pod bound
    K->>I1: start init-db
    I1-->>K: exit 0
    K->>I2: start init-migration
    I2-->>K: exit 0
    K->>App: start app (containers)
    App-->>K: running

Init containers share the Pod’s volumes and network namespace with the main containers. They can write files to emptyDir volumes that the main containers read. They can wait for network services that the main containers will use.

Common use cases

Waiting for a dependency:

initContainers:
- name: wait-for-db
  image: busybox:1.36
  command:
  - sh
  - -c
  - |
    until nc -z db 5432; do
      echo "Waiting for db..."
      sleep 2
    done

The init container polls the database port until it’s reachable. The main container can then start with the guarantee that the database is up.

Running migrations:

initContainers:
- name: migrate
  image: migrate/migrate:4
  command: ["migrate", "-path=/migrations", "-database=postgres://...", "up"]
  volumeMounts:
  - name: migrations
    mountPath: /migrations
volumes:
- name: migrations
  configMap:
    name: db-migrations

The init container runs the migration tool against the database. The main container can then start with the schema up to date.

Generating configuration:

initContainers:
- name: render-config
  image: alpine:3.19
  command:
  - sh
  - -c
  - |
    envsubst < /template/config.tpl > /config/app.conf
  volumeMounts:
  - name: template
    mountPath: /template
  - name: config
    mountPath: /config
volumes:
- name: template
  configMap:
    name: app-config-template
- name: config
  emptyDir: {}

The init container renders a configuration template with environment variables and writes it to a shared volume. The main container reads the rendered configuration.

Fetching secrets:

initContainers:
- name: fetch-secret
  image: vault:1.15
  command:
  - sh
  - -c
  - |
    vault read -format=json secret/data/app > /secret/app.json
  volumeMounts:
  - name: secret
    mountPath: /secret
volumes:
- name: secret
  emptyDir: {}

The init container fetches a secret from Vault and writes it to a shared volume. The main container reads the secret.

Init container resources

initContainers:
- name: migrate
  image: migrate/migrate:4
  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      cpu: 500m
      memory: 512Mi

Init containers have separate resource requests and limits from the main containers. This is important because:

  • The scheduler uses the sum of all container (init + main) requests to decide if a node has capacity.
  • Init containers run briefly and may need different resources than the long-running main container.
  • A migration may need 1 GB of memory briefly; the main container may need only 256 MB sustained.

The “effective” requests for scheduling are the largest of any init container and the largest of any main container:

flowchart LR
    Init1[init container 1<br/>requests] --> Sum[Sum per resource]
    Init2[init container 2<br/>requests] --> Sum
    Main[main container<br/>requests] --> Sum
    Sum --> Sched[Used for scheduling]

For a Pod with:

  • init-1: requests cpu=100m, memory=256Mi
  • init-2: requests cpu=200m, memory=512Mi
  • main: requests cpu=500m, memory=1Gi

The scheduler uses:

  • cpu: max(100m, 200m) + 500m = 700m (init runs sequentially, so the largest init is the per-Pod init request; main runs concurrently with no init)
  • memory: same logic

Actually, the formula is more nuanced. The kubelet calculates the effective requests as the max of (sum of init requests) and (sum of all requests). For sequential init containers:

  • The sum of init requests at any moment is the largest init request (since they run one at a time).
  • The main containers’ requests add on top.
  • So effective requests = max init requests + main requests.

The exact formula is documented in the Pod resource specification. For practical purposes:

  • Resource requests for init containers are typically smaller than main containers (they run briefly).
  • If an init container needs significant resources, consider its impact on scheduling.

Init container vs readiness probe

A common question: should I wait for a dependency with an init container or a readiness probe?

# Init container: blocks Pod start until dependency is up
initContainers:
- name: wait-for-db
  image: busybox:1.36
  command: ["sh", "-c", "until nc -z db 5432; do sleep 2; done"]

vs

# Readiness probe: Pod starts, but is not Ready until dependency is up
readinessProbe:
  exec:
    command: ["sh", "-c", "nc -z db 5432"]

The difference:

  • Init container: blocks the Pod from starting main containers. The Pod is Pending (not Running) until the init succeeds.
  • Readiness probe: the Pod starts main containers, but is not Ready until the probe succeeds. The Pod is Running but not in Endpoints.

Use init container when the main container cannot function without the dependency (e.g., a migration must run before the app starts). Use readiness probe when the main container can start but should not receive traffic until the dependency is up.

Diagnosing init container failures

A Pod stuck in Pending with Initialized: False:

kubectl describe pod web-7c8
# Events:
#   ... reason: Failed   message: Error: failed to start container "init-migration": ...

Or:

kubectl get pod web-7c8 -o jsonpath='{.status.initContainerStatuses}' | jq

Output:

[
  {
    "name": "init-migration",
    "state": {
      "terminated": {
        "exitCode": 1,
        "reason": "Error",
        "finishedAt": "..."
      }
    },
    "lastState": {},
    "ready": false,
    "restartCount": 5
  }
]

The init container exited with code 1; the kubelet retried 5 times. The diagnosis:

kubectl logs web-7c8 -c init-migration --previous

Common failure modes:

  • Dependency unreachable: the wait-for-db init container is stuck; check the database’s status.
  • Migration script error: the migration tool failed; check the logs.
  • ConfigMap not found: the init container mounts a ConfigMap that does not exist. Check kubectl get configmap.
  • Image pull failure: the init container’s image is missing or the registry is unreachable.

Production patterns

Long-running init with timeout:

initContainers:
- name: wait-for-db
  image: busybox:1.36
  command:
  - sh
  - -c
  - |
    timeout 300 sh -c 'until nc -z db 5432; do sleep 2; done' || exit 1

The init container has a 300s timeout. If the database is not up in 5 minutes, the init fails and the Pod retries.

Conditional init with success/failure semantics:

initContainers:
- name: check-config
  image: busybox:1.36
  command:
  - sh
  - -c
  - |
    if [ -f /config/ready ]; then
      echo "Config ready"
      exit 0
    else
      echo "Config missing"
      exit 1
    fi
  volumeMounts:
  - name: config
    mountPath: /config

The init succeeds if the config file exists, fails otherwise.

Cross-course references

  • The Linux course part XXII-Linux-NetTroubleshoot covers dependency waiting; init containers are the cluster-level equivalent.
  • The Ansible course part XX-Ansible-SSH covers setup tasks; init containers are the cluster-level equivalent.
  • The Docker course part XXIX-Docker-Build covers image layers; init containers run additional images sequentially.

Quiz

Knowledge check · 4 questions

  1. Q1. How do init containers run relative to the main containers and to each other?

  2. Q2. An init container can be restarted while it is running if it hits a transient error.

  3. Q3. A team's init container runs a database migration. The migration succeeds on the first Pod, but subsequent Pods fail because the migration tool tries to apply migrations that already ran. Diagnose.

    Deployment has 3 replicas. The init container runs `migrate -path=/migrations up`. The first Pod's init succeeds; the migration runs. The second and third Pods' init containers try to run the same migration and fail with 'migration already applied' (exit code 1).

  4. Q4. When is the right tool an init container for waiting on a dependency, and when is the right tool a readiness probe?

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Use init containers for blocking setup. Migrations, config generation, dependency waiting.
  • Make init containers idempotent. They may run multiple times due to retries; non-idempotent init causes Pod failures.
  • Size init container resources separately from main containers. They run briefly and may need different resources.
  • Add timeouts to wait-for-dependency init containers. An infinite wait deadlocks the Pod.
  • Diagnose init failures with kubectl logs -c &lt;name&gt; --previous. The init’s logs are the diagnostic key.