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
| Dimension | Init Container | Sidecar Container |
|---|---|---|
| Lifecycle | Runs to completion before main containers start, then terminates | Runs for the entire lifetime of the Pod alongside the main container |
| Execution Order | Runs sequentially — each must succeed before the next starts | Runs in parallel with the main container |
| Purpose | One-time initialization: dependency checks, config generation, schema setup | Ongoing support: log shipping, proxying, monitoring, syncing |
| Restart Behavior | All init containers re-run if any fails (depending on restartPolicy) | Restarts independently if it crashes, without affecting main container startup |
| Resource Impact | Resources freed after completion — not counted during main container runtime | Consumes resources for the entire Pod lifetime |
| Spec Location | Defined under spec.initContainers | Defined under spec.containers (native sidecar uses spec.initContainers with restartPolicy: Always) |
| Probes | No support for liveness, readiness, or startup probes | Full 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:
wait-for-dbruns first and waits for PostgreSQL to be reachable- Only after it exits successfully,
run-migrationsruns the database migrations - Only after migrations succeed, the
appcontainer 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.containersstart 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:
envoy-proxystarts (native sidecar). Because it hasrestartPolicy: Always, it starts and keeps running instead of blocking the next init container.- Once
envoy-proxypasses its startup probe,prepare-configruns (regular init container). - After
prepare-configcompletes, the mainappcontainer starts. envoy-proxycontinues running alongsideappfor the Pod's lifetime.- On shutdown,
appterminates first, thenenvoy-proxyterminates 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.”