Init Container vs Sidecar Container

Key Differences in Kubernetes

Init containers run to completion before the main containers start — they perform one-time setup tasks like waiting for dependencies, loading configuration, or initializing databases. Sidecar containers run alongside the main container for the lifetime of the Pod, providing ongoing support like log shipping, proxy routing, or metrics collection.

Side-by-Side Comparison

DimensionInit ContainerSidecar Container
LifecycleRuns to completion before main containers start, then terminatesRuns for the entire lifetime of the Pod alongside the main container
Execution OrderRuns sequentially — each must succeed before the next startsRuns in parallel with the main container
PurposeOne-time initialization: dependency checks, config generation, schema setupOngoing support: log shipping, proxying, monitoring, syncing
Restart BehaviorAll init containers re-run if any fails (depending on restartPolicy)Restarts independently if it crashes, without affecting main container startup
Resource ImpactResources freed after completion — not counted during main container runtimeConsumes resources for the entire Pod lifetime
Spec LocationDefined under spec.initContainersDefined under spec.containers (native sidecar uses spec.initContainers with restartPolicy: Always)
ProbesNo support for liveness, readiness, or startup probesFull support for all probe types

Detailed Breakdown

Init Container Basics

Init containers are defined separately from regular containers and run in order before any main container starts:

apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          until nc -z postgres-service 5432; do
            echo "Waiting for database..."
            sleep 2
          done
    - name: run-migrations
      image: my-app:1.0.0
      command: ["python", "manage.py", "migrate"]
      env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-creds
              key: url
  containers:
    - name: app
      image: my-app:1.0.0
      ports:
        - containerPort: 8080

The sequence is strict:

  1. wait-for-db runs first and waits for PostgreSQL to be reachable
  2. Only after it exits successfully, run-migrations runs the database migrations
  3. Only after migrations succeed, the app container starts

If any init container fails, the Pod restarts (based on restartPolicy) and all init containers run again from the beginning.

Traditional Sidecar Pattern

Before Kubernetes 1.28, sidecars were simply additional containers in the containers array:

apiVersion: v1
kind: Pod
metadata:
  name: app-with-logging
spec:
  containers:
    - name: app
      image: my-app:1.0.0
      ports:
        - containerPort: 8080
      volumeMounts:
        - name: logs
          mountPath: /var/log/app
    - name: log-shipper
      image: fluentd:v1.16
      volumeMounts:
        - name: logs
          mountPath: /var/log/app
          readOnly: true
      resources:
        requests:
          cpu: 50m
          memory: 64Mi
  volumes:
    - name: logs
      emptyDir: {}

The app writes logs to /var/log/app, and the Fluentd sidecar reads and ships them to an external system. Both containers run for the entire Pod lifetime and share the logs volume.

The Problem with Traditional Sidecars

Traditional sidecars had ordering issues:

  • All containers in spec.containers start roughly at the same time
  • The sidecar might not be ready when the main container needs it
  • When the Pod shuts down, the sidecar might terminate before the main container finishes draining

This caused problems for service mesh proxies like Istio's Envoy sidecar — the main container might try to make network calls before Envoy was ready, or Envoy might shut down while the main container was still handling requests.

Native Sidecar Containers (Kubernetes 1.28+)

Kubernetes 1.28 introduced native sidecar containers that solve the ordering problem. They are defined as init containers with restartPolicy: Always:

apiVersion: v1
kind: Pod
metadata:
  name: app-with-native-sidecar
spec:
  initContainers:
    - name: envoy-proxy
      image: envoyproxy/envoy:v1.28
      restartPolicy: Always  # This makes it a native sidecar
      ports:
        - containerPort: 15001
      resources:
        requests:
          cpu: 100m
          memory: 128Mi
      startupProbe:
        httpGet:
          path: /ready
          port: 15001
        periodSeconds: 2
    - name: prepare-config
      image: busybox:1.36
      command: ["sh", "-c", "echo 'config ready' > /config/ready"]
      volumeMounts:
        - name: config
          mountPath: /config
  containers:
    - name: app
      image: my-app:1.0.0
      ports:
        - containerPort: 8080
  volumes:
    - name: config
      emptyDir: {}

The startup sequence is:

  1. envoy-proxy starts (native sidecar). Because it has restartPolicy: Always, it starts and keeps running instead of blocking the next init container.
  2. Once envoy-proxy passes its startup probe, prepare-config runs (regular init container).
  3. After prepare-config completes, the main app container starts.
  4. envoy-proxy continues running alongside app for the Pod's lifetime.
  5. On shutdown, app terminates first, then envoy-proxy terminates last.

This guarantees the sidecar is ready before the main container starts and outlives it during shutdown.

Sharing Data Between Init and Main Containers

Init containers commonly populate shared volumes that main containers consume:

spec:
  initContainers:
    - name: git-clone
      image: alpine/git:latest
      command:
        - git
        - clone
        - --depth=1
        - https://github.com/org/config-repo.git
        - /config
      volumeMounts:
        - name: config
          mountPath: /config
  containers:
    - name: app
      image: my-app:1.0.0
      volumeMounts:
        - name: config
          mountPath: /app/config
          readOnly: true
  volumes:
    - name: config
      emptyDir: {}

The init container clones the configuration repository. The main container reads the config. This is a one-time operation — if you need continuous syncing, use a sidecar with something like git-sync.

Resource Accounting

Init container resources are calculated differently from sidecar resources:

  • Init containers: Kubernetes takes the maximum of all init container resource requests (not the sum) because they run sequentially. If init container A requests 500Mi and init container B requests 200Mi, the effective init resource is 500Mi.
  • Sidecar containers: Resources are summed with the main containers because they run concurrently. A main container requesting 256Mi plus a sidecar requesting 128Mi means the Pod needs 384Mi.
# Effective Pod resources = max(init containers) vs sum(regular containers)
# max(500Mi, 200Mi) = 500Mi (init phase)
# 256Mi + 128Mi = 384Mi (running phase)
# Pod requests = max(500Mi, 384Mi) = 500Mi

Common Init Container Patterns

# Wait for a service to be ready
- name: wait-for-service
  image: busybox:1.36
  command: ['sh', '-c', 'until nslookup redis-service; do sleep 2; done']

# Set file permissions
- name: fix-permissions
  image: busybox:1.36
  command: ['sh', '-c', 'chown -R 1000:1000 /data']
  securityContext:
    runAsUser: 0
  volumeMounts:
    - name: data
      mountPath: /data

# Download artifacts
- name: download-model
  image: curlimages/curl:latest
  command: ['curl', '-o', '/models/model.bin', 'https://storage.example.com/model.bin']
  volumeMounts:
    - name: models
      mountPath: /models

Common Sidecar Patterns

# Service mesh proxy
- name: envoy
  image: envoyproxy/envoy:latest
  restartPolicy: Always

# Log aggregation
- name: fluentd
  image: fluentd:latest

# Configuration sync
- name: config-sync
  image: git-sync:latest
  args: ["--repo=https://github.com/org/config", "--period=60s"]

# Database proxy
- name: cloud-sql-proxy
  image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
  args: ["--port=5432", "project:region:instance"]

Use Init Container when...

  • Waiting for a database or external service to become available
  • Downloading or generating configuration files before the app starts
  • Running database migrations or schema initialization
  • Cloning a Git repo or fetching artifacts before the main workload
  • Setting file permissions or populating a shared volume

Use Sidecar Container when...

  • Running a service mesh proxy like Envoy or Istio
  • Shipping logs from the main container to an external system
  • Providing a local caching proxy (e.g., cloud-sql-proxy)
  • Collecting and exporting metrics
  • Syncing configuration from an external source continuously

Model Interview Answer

Init containers and sidecar containers serve different phases of a Pod's lifecycle. Init containers run sequentially before any main containers start — they handle one-time setup like waiting for a dependency, generating config files, or running database migrations. Once all init containers complete successfully, the main containers start. Sidecar containers, on the other hand, run alongside the main container for the entire Pod lifetime. They handle ongoing concerns like log shipping, service mesh proxying, or metrics collection. In Kubernetes 1.28+, native sidecar containers are defined as init containers with restartPolicy: Always, which means they start before the main container but keep running — solving the old problem of sidecars sometimes starting after or terminating before the main container.

Related Comparisons