Skip to main content

Managing Multi-Container Pods with Init Containers and Sidecars

A Kubernetes pod can run more than one container. Two patterns govern how these containers are used: Init Containers run sequentially before your app starts — used for setup tasks and dependency checks. Sidecars run alongside your app for the entire pod lifetime — used for logging, proxying, and metrics collection. Understanding both patterns eliminates entire categories of startup bugs and observability gaps.

52 Terms

A Kubernetes pod can run more than one container. Two patterns govern how these containers are used: Init Containers run sequentially before your app starts — used for setup tasks and dependency checks. Sidecars run alongside your app for the entire pod lifetime — used for logging, proxying, and metrics collection. Understanding both patterns eliminates entire categories of startup bugs and observability gaps.

+++

Managing Multi-Container Pods with Init Containers and Sidecars

Why Multiple Containers in One Pod

A pod is not just a container — it is a group of containers that share a network namespace and optional storage volumes. All containers in a pod have the same IP address and can reach each other on localhost.

◈ DIAGRAM
Pod: payments-api
+--------------------------------------------------+
| Shared network (localhost) |
| Shared volumes (/var/log, /tmp/config) |
| |
| +------------------+ +---------------------+ |
| | payments-api | | log-forwarder | |
| | (main app) | | (sidecar) | |
| | port 8080 | | reads /var/log/*.log | |
| +------------------+ +---------------------+ |
| |
+--------------------------------------------------+

The two containers do not need to know about each other — they cooperate through shared filesystem paths and localhost networking.

Init Containers — Sequential Setup Before Your App Starts

An Init Container runs to completion before any regular container in the pod starts. If the init container fails, Kubernetes restarts it (subject to the pod's restartPolicy) until it succeeds. The main app container never starts until all init containers have completed successfully.

◈ DIAGRAM
Pod startup sequence with init containers:
[Init: 0/2] init-wait-for-db → runs → SUCCESS
[Init: 1/2] init-run-migration → runs → SUCCESS
[Running] payments-api → starts ← Only after both inits complete
If init-wait-for-db fails:
[Init: 0/2] init-wait-for-db → runs → FAILS → retry → retry → retry...
payments-api never starts

Init Container Pattern 1 — Wait for a Dependency

The most common init container. Your app requires a database or a config service to be ready. Without this check, your app starts, fails to connect, and enters CrashLoopBackOff.

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: payments-api
template:
spec:
initContainers:
- name: init-wait-for-postgres
image: busybox:1.36
command:
- sh
- -c
- |
echo "Waiting for PostgreSQL to be ready..."
until nc -z postgres-service 5432; do
echo "PostgreSQL not ready — retrying in 5s"
sleep 5
done
echo "PostgreSQL is ready"
- name: init-wait-for-redis
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z redis-service 6379; do
sleep 2
done
echo "Redis is ready"
containers:
- name: payments-api
image: registry.razorpay.in/payments-api:v2.5.1
ports:
- containerPort: 8080
Bash
kubectl get pod payments-api-7d9f8b-xk2p9 -n production -w
# Output:
# NAME READY STATUS RESTARTS AGE
# payments-api-7d9f8b-xk2p9 0/1 Init:0/2 0 5s
# payments-api-7d9f8b-xk2p9 0/1 Init:1/2 0 12s
# payments-api-7d9f8b-xk2p9 0/1 PodInitializing 0 15s
# payments-api-7d9f8b-xk2p9 1/1 Running 0 16s
# Read init container logs specifically
kubectl logs payments-api-7d9f8b-xk2p9 \
-c init-wait-for-postgres \
-n production

Init Container Pattern 2 — Fetch Config at Startup

An init container downloads a config file from a secret store (Vault) or an S3 bucket and writes it to a shared volume that the main app reads.

YAML
spec:
volumes:
- name: app-config
emptyDir: {} # Temporary volume shared between init and main container
initContainers:
- name: init-fetch-config
image: vault:1.15
command:
- sh
- -c
- |
vault kv get -field=config secret/payments-api/production \
> /config/app.yaml
echo "Config written to /config/app.yaml"
volumeMounts:
- name: app-config
mountPath: /config
env:
- name: VAULT_ADDR
value: "http://vault.internal:8200"
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-token
key: token
containers:
- name: payments-api
image: registry.razorpay.in/payments-api:v2.5.1
volumeMounts:
- name: app-config
mountPath: /etc/app # App reads config from here
◈ DIAGRAM
Init container: Main container:
writes → /config/app.yaml reads ← /etc/app/app.yaml
(via shared emptyDir volume)

Init Container Pattern 3 — Database Migration Before Deploy

The most critical pattern at companies like Zerodha or PhonePe. Schema migrations must complete before the new app version starts handling requests.

YAML
initContainers:
- name: init-db-migration
image: registry.razorpay.in/payments-api:v2.5.1 # Same image as the app
command: ["python", "manage.py", "migrate", "--no-input"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url

This is simpler than a separate Job when you want the migration to be tightly coupled to each pod's lifecycle — though for large clusters, a separate Job (as covered in the Jobs topic) gives more control.

Sidecar Containers — Run Alongside the App Forever

A sidecar starts when the pod starts and runs for the entire pod lifetime alongside the main application. It handles a cross-cutting concern — logging, metrics, service mesh proxy — without the main app needing to know about it.

◈ DIAGRAM
Timeline:
Pod starts
│
├── [init-wait-for-db] runs → completes
│
├── [payments-api] starts ──────────────────────────── runs forever
├── [log-forwarder] starts ──────────────────────────── runs forever
└── [envoy-proxy] starts ──────────────────────────── runs forever
│
Pod terminates

Sidecar Pattern 1 — Log Forwarding

Your app writes logs to files on disk. A sidecar reads those files and ships them to a central system (Elasticsearch, Loki, Datadog).

YAML
spec:
volumes:
- name: app-logs
emptyDir: {}
containers:
- name: payments-api
image: registry.razorpay.in/payments-api:v2.5.1
volumeMounts:
- name: app-logs
mountPath: /var/log/app # App writes logs here
- name: log-forwarder
image: fluent/fluent-bit:2.2
volumeMounts:
- name: app-logs
mountPath: /var/log/app # Sidecar reads from the same path
env:
- name: LOKI_URL
value: "http://loki.monitoring:3100"
◈ DIAGRAM
payments-api → writes → /var/log/app/payments.log
↑
log-forwarder → reads → /var/log/app/payments.log → ships to Loki

Sidecar Pattern 2 — Service Mesh Proxy (Envoy / Istio)

In a service mesh, a sidecar proxy intercepts all network traffic to and from the pod. This is how Istio works — Envoy is automatically injected as a sidecar into every pod in a mesh-enabled namespace.

YAML
# Istio injects this automatically — shown here for understanding
containers:
- name: payments-api
image: registry.razorpay.in/payments-api:v2.5.1
ports:
- containerPort: 8080
- name: envoy-proxy # Injected by Istio automatically
image: envoyproxy/envoy:v1.28
ports:
- containerPort: 15001 # Intercepts all outbound traffic
- containerPort: 15006 # Intercepts all inbound traffic
◈ DIAGRAM
Inbound request:
Internet → Envoy (sidecar) → payments-api app
↑
Enforces mTLS
Records metrics
Applies retry policy
Outbound request:
payments-api app → Envoy (sidecar) → payments-db
↑
Applies circuit breaker
Load balances
Encrypts with mTLS

Sidecar Pattern 3 — Secrets Refresh Without Restart

A sidecar periodically fetches updated secrets from Vault and writes them to a shared volume. The main app reads secrets from files rather than environment variables — so it picks up rotated secrets without a pod restart.

YAML
spec:
volumes:
- name: secrets-volume
emptyDir:
medium: Memory # Store secrets in memory, not on disk
containers:
- name: payments-api
image: registry.razorpay.in/payments-api:v2.5.1
volumeMounts:
- name: secrets-volume
mountPath: /var/secrets
readOnly: true
- name: vault-agent
image: vault:1.15
command: ["vault", "agent", "-config=/etc/vault/config.hcl"]
volumeMounts:
- name: secrets-volume
mountPath: /var/secrets # Writes refreshed secrets here every 15 minutes

Resource Allocation — Init vs Sidecar

Init containers are sequential — only one runs at a time. The pod's effective resource request for init containers is the maximum of any single init container, not the sum.

TEXT
Init containers:
init-wait-db: cpu: 50m, memory: 64Mi
init-migration: cpu: 200m, memory: 256Mi
Effective init resource request = max(50m, 200m) = 200m CPU, max(64Mi, 256Mi) = 256Mi
Regular containers (run simultaneously):
payments-api: cpu: 500m, memory: 512Mi
log-forwarder: cpu: 100m, memory: 128Mi
Effective regular resource request = sum = 600m CPU, 640Mi memory
Total pod resource request = max(init_max, regular_sum) per resource
= 600m CPU (regular wins), 640Mi memory (regular wins)

Kubernetes 1.29+ Native Sidecar Containers

From Kubernetes 1.29, sidecars can be declared as initContainers with restartPolicy: Always. This is the new native sidecar gate — it starts before the main app but runs for the pod's lifetime.

YAML
initContainers:
- name: log-forwarder
image: fluent/fluent-bit:2.2
restartPolicy: Always # Makes this a native sidecar (K8s 1.29+)
# Starts before main containers, runs forever alongside them
# Kubernetes waits for it to be Ready before starting main containers
containers:
- name: payments-api
image: registry.razorpay.in/payments-api:v2.5.1

The advantage over the old pattern: Kubernetes now understands the startup ordering — the sidecar is fully up before the main app starts, and on shutdown, the main app terminates first, then the sidecar.

Debugging Init Containers

Bash
# Pod stuck in Init:0/2 — read the first init container's logs
kubectl logs <pod-name> -c init-wait-for-postgres -n production
# If the init container has not started yet (CrashLoopBackOff)
kubectl logs <pod-name> -c init-wait-for-postgres -n production --previous
# Describe the pod for events
kubectl describe pod <pod-name> -n production
# Look for: "Back-off restarting failed init container"

🔴 Common Mistake: Using an init container to wait for a service by hostname (e.g. postgres-service) without ensuring DNS is available in the init container. Use nslookup postgres-service in the init container to verify DNS resolves before attempting the TCP check.

💡 Tip: At Swiggy or Hotstar, sidecar resource requests are often misconfigured — teams set zero requests on the log forwarder sidecar to "save resources." The result is the sidecar gets OOMKilled under load and stops shipping logs precisely when you need them most during an incident. Always set meaningful resource requests and limits on sidecar containers, not just the main app container.

Resources

Velero vs etcd Snapshot: Not a Real Choice

Velero vs etcd Snapshot: Not a Real Choice

Velero and etcd snapshots protect different layers of a cluster, not the same thing. Here's what each covers and why production DR needs both.

5 min read•Aug 2026
Istio Ambient vs Linkerd in 2026

Istio Ambient vs Linkerd in 2026

Istio Ambient killed the sidecar-tax argument. The real 2026 decision is waypoint topology and Buoyant's licensing shift, not features vs simplicity.

5 min read•Aug 2026
Nginx Ingress vs Traefik vs Gateway API in 2026

Nginx Ingress vs Traefik vs Gateway API in 2026

Ingress-nginx retired in March 2026. Here's how Traefik and the Gateway API actually compare as replacements — and why "just swap it" is the wrong frame.

5 min read•Aug 2026
OPA Gatekeeper vs Kyverno: Policy Engine in 2026

OPA Gatekeeper vs Kyverno: Policy Engine in 2026

OPA Gatekeeper vs Kyverno compared for 2026 - Rego vs YAML, mutation maturity, operational overhead, and which policy engine fits your cluster.

5 min read•Aug 2026
Helm vs Kustomize in 2026: Templating vs Patching

Helm vs Kustomize in 2026: Templating vs Patching

Helm vs Kustomize compared for 2026 - templating vs patching, Helm 4's new features, and why most production teams end up running both.

5 min read•Aug 2026
Prometheus vs Datadog vs New Relic: Real Costs

Prometheus vs Datadog vs New Relic: Real Costs

Prometheus, Datadog, and New Relic compared for 2026 - real pricing at scale, hidden cost drivers, and which fits a Kubernetes-heavy stack.

5 min read•Aug 2026
Cluster Autoscaler vs Karpenter for EKS in 2026

Cluster Autoscaler vs Karpenter for EKS in 2026

Cluster Autoscaler vs Karpenter compared for EKS in 2026 - provisioning speed, bin-packing, cloud support, and when each is the right default.

5 min read•Aug 2026
GKE vs EKS vs AKS in 2026: Which Fits Your Team?

GKE vs EKS vs AKS in 2026: Which Fits Your Team?

GKE, EKS, and AKS compared for 2026 - control plane pricing, Autopilot vs Karpenter vs Node Auto Provisioning, and which platform actually fits your team.

5 min read•Aug 2026
K3s vs K8s vs MicroK8s in 2026

K3s vs K8s vs MicroK8s in 2026

K3s, full Kubernetes, and MicroK8s compared for 2026 - resource footprint, production readiness, and which fits edge, homelab, or cloud workloads.

5 min read•Aug 2026
Canary Deployments with Argo Rollouts & Flagger

Canary Deployments with Argo Rollouts & Flagger

Ship to 5% of users first and auto-rollback in minutes — a hands-on guide to canary deployments with Argo Rollouts and Flagger on Kubernetes.

5 min read•Jun 2026
OpenTelemetry Explained: Metrics, Logs, Traces

OpenTelemetry Explained: Metrics, Logs, Traces

OpenTelemetry unifies metrics, logs, and traces under one open standard — how it works, what it replaces, and how to instrument a service in 20 minutes.

5 min read•Jun 2026
Kubernetes Cost Optimization Without Breaking SLOs

Kubernetes Cost Optimization Without Breaking SLOs

Average Kubernetes CPU utilization across production clusters is 8%. Here is the complete 2026 playbook for cutting cloud spend without touching your SLOs.

5 min read•Jun 2026
ArgoCD vs FluxCD: GitOps for Kubernetes in 2026

ArgoCD vs FluxCD: GitOps for Kubernetes in 2026

ArgoCD and FluxCD are the two dominant GitOps engines for Kubernetes in 2026 — this breakdown tells you exactly which one to pick and why.

10 min read•Jun 2026

Explore More in Kubernetes Workload Management

All 6 Topics

Frequently Asked Questions

Is Managing Multi-Container Pods with Init Containers and Sidecars free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Managing Multi-Container Pods with Init Containers and Sidecars topic cover?

A Kubernetes pod can run more than one container. Two patterns govern how these containers are used: Init Containers run sequentially before your app starts — used for setup tasks and dependency checks. Sidecars run alongside your app for the entire pod lifetime — used for logging, proxying, and metrics collection. Understanding both patterns eliminates entire categories of startup bugs and observability gaps.