Sign in
← Kubernetes: configuration
6 min · easy · Lesson 6 of 8

Init containers

Containers that run before the main ones: start order, common scenarios, and turning an init container into a native sidecar.

Why this matters

An app often needs to do something before it starts: wait for a database, download files, build a config from a template. You could hide that in a bash wrapper inside the main image. Then the app's image carries utilities and logic it doesn't need once it's running.

Kubernetes gives preparation its own place: init containers. They run before the main containers, each with its own image and its own command. If the preparation fails, the app never starts at all, and you can see why in the Pod's status.

What an initContainer is

An init container (initContainer) is a Pod container that has to exit with code 0 before the main containers start. Only after that does kubelet bring up the regular containers.

The rules:

An init container and a main container can share an emptyDir volume: the init container puts files there, the main container reads them. That's how you pass downloaded assets and generated configs between them.

Minimal YAML

A Deployment whose main container waits for Service db on port 5432:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      initContainers:
        - name: wait-for-db
          image: busybox:1.36.1
          command:
            - sh
            - -c
            - 'until nc -z -w 2 db 5432; do echo waiting; sleep 2; done'
      containers:
        - name: main
          image: busybox:1.36.1
          command: ["sh", "-c", "sleep 3600"]

initContainers sits in spec.template.spec next to containers and is described with the same fields: name, image, command, volumeMounts.

nc -z host port tries to open a TCP connection and closes it right away, without sending any data. If the connection doesn't open, nc exits with a nonzero code, and until tries again. It's the cheapest way to "wait for a port", and it works in any busybox. The -w 2 flag caps one attempt at two seconds. Without it, an attempt against an unreachable host can hang for a long time.

If the dependency never shows up, the init container loops forever. The Pod sits in Init:0/1 and looks broken from the outside. The only place you can see why is the init container's log: kubectl logs <pod> -c wait-for-db.

Init doesn't count toward READY

The STATUS column in kubectl get pod shows which init container the Pod is on right now. Here's a Pod with two init containers, the second one waiting on a database:

NAME     READY   STATUS            RESTARTS   AGE
phases   0/1     Init:0/2          0          5s
phases   0/1     Init:1/2          0          15s
phases   0/1     PodInitializing   0          35s
phases   1/1     Running           0          42s

Init:1/2 means one of the two init containers has already finished. PodInitializing shows up once all init containers have passed and the main containers are still starting.

A regular init container doesn't go into the READY count: the Pod above has two init containers and one main container, and the column still reads 1/1. readinessProbe isn't supported on a regular init container: the API rejects such a manifest with the error may not be set for init containers without restartPolicy=Always. The exception is the sidecar form (see below): its probes do work, and it counts toward READY.

If an init container fails, the status becomes Init:Error, and after a few retries, Init:CrashLoopBackOff.

Check an init container's status separately from the main containers:

kubectl get pod <name> -o jsonpath='{.status.initContainerStatuses[*].name}'
kubectl describe pod <name>

In describe, init containers have their own Init Containers section with State, Last State, and Exit Code fields.

What usually goes in init containers

restartPolicy: Always turns init into a sidecar

An init container has its own restartPolicy field. Set it to Always, and the container stops being a one-shot. It starts before the main containers, runs for the Pod's whole lifetime, and stops after them. This is a native sidecar, a stable feature since Kubernetes 1.33.

initContainers:
  - name: log-shipper
    image: busybox:1.36.1
    restartPolicy: Always
    command: ["sh", "-c", "tail -F /var/log/app/app.log"]

How a sidecar differs from a regular init container:

time ------------------------------>

regular init:
  init-1  [==]
  init-2      [==]
  main            [==============]

init with restartPolicy: Always:
  sidecar [============================]
  main            [==============]

This is how log agents, mesh proxies, and metrics exporters are described. Without restartPolicy, an init container stays regular: it runs, exits, and disappears. More on sidecars in the next lesson.

Init container resources

Init containers run one at a time and before the main ones, so their requests don't add up with the main containers' requests. The scheduler takes the Pod's effective request as the larger of two numbers: the biggest request among the init containers, and the sum of the main containers' requests.

Example: an init container asks for 1500m CPU, the main container for 100m. The Pod will take up 1500m on the node, even though only the main container is actually working after startup. A heavy init container makes the whole Pod heavy.

A sidecar init container behaves like a main container in this count: it runs alongside them, so its request gets added to the sum.

Anti-pattern: database migrations in init

Long database migrations don't belong in an init container. Init runs on every new Pod: on every rollout, every re-creation, every replica, and replicas run it at the same time. While the migration runs, the Pod isn't Ready, and three replicas end up trying to migrate the same database in parallel.

Migrations go in a separate Job that runs once before the Deployment rolls out.

kubectl get pod -w                      # shows phases Init:0/1, PodInitializing, Running
kubectl describe pod <name>             # Init Containers section
kubectl logs <pod> -c wait-for-db       # init container's log
kubectl explain pod.spec.initContainers

kubectl logs <pod> without -c shows the main container. While the Pod is still at the init stage, you can't see the init container's log this way:

Defaulted container "main" out of: main, first (init), wait-for-db (init)
Error from server (BadRequest): container "main" in pod "phases" is waiting to start: PodInitializing

What commonly breaks

What you'll need for the tasks

Homework

Practice the theory on your own cluster: 2 steps with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-init-container     # set up the starting state
./run.sh verify k8s-init-container    # check the current step

In short

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-config-06-init-container