Understanding Sidecars in Kubernetes

Sidecar in Kubernetes

What is a Sidecar?

A sidecar is an additional container running inside the same pod as your main application container — sharing the same network, storage, and lifecycle.

┌─────────────────────────────────────────┐
│ POD │
│ │
│ ┌─────────────────┐ ┌───────────────┐ │
│ │ Main App │ │ Sidecar │ │
│ │ Container │ │ Container │ │
│ │ │ │ │ │
│ │ (your code) │ │ (helper code) │ │
│ └─────────────────┘ └───────────────┘ │
│ │
│ Shared: Network (localhost) │ Volumes │
└─────────────────────────────────────────┘

The name comes from a motorcycle sidecar — it’s attached to the main vehicle, travels with it, but serves a separate purpose.


Why Use a Sidecar?

The core idea is separation of concerns — your app does business logic, the sidecar handles cross-cutting concerns.

WITHOUT sidecar: WITH sidecar:
┌────────────────────┐ ┌──────────────┐ ┌──────────────┐
│ App Container │ │ App │ │ Sidecar │
│ │ │ (just biz │ │ - logging │
│ - business logic │ │ logic) │ │ - metrics │
│ - logging │ │ │ │ - mTLS │
│ - metrics │ │ │ │ - tracing │
│ - mTLS │ │ │ │ │
│ - tracing │ └──────────────┘ └──────────────┘
│ - config reload │
└────────────────────┘

How Containers Share Resources in a Pod

Pod Network Namespace
├── localhost (127.0.0.1) shared by ALL containers
├── Same IP address
└── Same port space (containers can't use same port)
Shared Volumes
├── emptyDir — shared scratch space
├── configMap / secret — shared config
└── PersistentVolume — shared storage
Process isolation
└── Containers have separate filesystems and processes
(unless shareProcessNamespace: true)

Common Sidecar Patterns


Pattern 1 — Proxy / Service Mesh Sidecar

The most common use case — Envoy or linkerd-proxy intercepts all network traffic.

Incoming request
┌─────────────────────────────────────┐
│ POD │
│ ┌──────────────┐ ┌─────────────┐ │
│ │ Envoy │ │ App │ │
│ │ Sidecar │──▶ Container │ │
│ │ │ │ │ │
│ │ - mTLS │ │ sees plain │ │
│ │ - retries │ │ HTTP only │ │
│ │ - metrics │ │ │ │
│ │ - tracing │ └─────────────┘ │
│ └──────────────┘ │
└─────────────────────────────────────┘
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
# Main app
- name: api
image: myapp:latest
ports:
- containerPort: 8080
# Proxy sidecar
- name: envoy-proxy
image: envoyproxy/envoy:v1.28
ports:
- containerPort: 9901 # admin
- containerPort: 15001 # outbound
- containerPort: 15006 # inbound
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
volumes:
- name: envoy-config
configMap:
name: envoy-config

Pattern 2 — Log Shipper Sidecar

App writes logs to a shared volume, sidecar tails and ships them.

┌──────────────────────────────────────────┐
│ POD │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ App │ │ Fluentd / │ │
│ │ Container │ │ Filebeat │ │
│ │ │ │ Sidecar │ │
│ │ writes logs │ │ │ │
│ │ to /var/log/ │ │ tails logs │ │
│ │ │ │ ships to │ │
│ └──────┬───────┘ │ Elasticsearch│ │
│ │ └──────▲───────┘ │
│ │ shared volume │ │
│ └──────────────────────┘ │
│ /var/log/app/ │
└──────────────────────────────────────────┘
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-with-logging
spec:
template:
spec:
containers:
# Main app — writes to shared volume
- name: api
image: myapp:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/app
# Log shipper sidecar
- name: log-shipper
image: fluent/fluentd:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/app # same volume
readOnly: true
env:
- name: ELASTICSEARCH_HOST
value: "elasticsearch.logging.svc"
volumes:
- name: log-volume
emptyDir: {} # shared scratch space

Pattern 3 — Config Watcher / Reloader

Sidecar watches for config changes and reloads the app without restarting the pod.

┌──────────────────────────────────────────┐
│ POD │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Nginx / │ │ Config │ │
│ │ App │◀──────│ Reloader │ │
│ │ │ SIGHUP│ Sidecar │ │
│ │ uses config │ │ │ │
│ │ from /config │ │ watches │ │
│ │ │ │ ConfigMap │ │
│ └──────────────┘ │ changes │ │
└──────────────────────────────────────────┘
      containers:
      # Main app
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: config
          mountPath: /etc/nginx

      # Config reloader sidecar
      - name: config-reloader
        image: jimmidyson/configmap-reload:latest
        args:
        - --volume-dir=/config
        - --webhook-url=http://localhost:80/-/reload
        volumeMounts:
        - name: config
          mountPath: /config
          readOnly: true

      volumes:
      - name: config
        configMap:
          name: nginx-config




Pattern 4 — Secret / Cert Sync Sidecar

Fetches secrets from Vault or Key Vault and writes them to a shared volume.

┌──────────────────────────────────────────┐
│ POD │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ App │ │ Vault │ │
│ │ │ │ Agent │ │
│ │ reads creds │ │ Sidecar │ │
│ │ from │ │ │ │
│ │ /vault/ │ │ fetches + │ │
│ │ secrets/ │ │ renews creds │ │
│ └──────┬───────┘ └──────▲───────┘ │
│ │ shared volume │ writes │
│ └──────────────────────┘ │
│ /vault/secrets/ │
└──────────────────────────────────────────┘
      containers:
      # Main app
      - name: api
        image: myapp:latest
        volumeMounts:
        - name: vault-secrets
          mountPath: /vault/secrets
          readOnly: true

      # Vault agent sidecar
      - name: vault-agent
        image: hashicorp/vault:latest
        args:
        - agent
        - -config=/vault/config/agent.hcl
        volumeMounts:
        - name: vault-secrets
          mountPath: /vault/secrets     # writes here
        - name: vault-config
          mountPath: /vault/config

      volumes:
      - name: vault-secrets
        emptyDir:
          medium: Memory               # in-memory — never on disk
      - name: vault-config
        configMap:
          name: vault-agent-config




Pattern 5 — Ambassador Sidecar

Acts as a local proxy to external services — simplifies connection logic in the app.

┌──────────────────────────────────────────┐
│ POD │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ App │ │ Ambassador │ │
│ │ │ │ Sidecar │ │
│ │ connects to │──────▶│ │ │
│ │ localhost: │ │ handles: │ │
│ │ 5432 │ │ - TLS │ │
│ │ (thinks it's │ │ - retries │ │
│ │ local DB) │ │ - failover │ │
│ └──────────────┘ └──────┬───────┘ │
└──────────────────────────────────────────┘
┌────────────▼──────────┐
│ Remote DB / Service │
│ (with mTLS, etc.) │
└───────────────────────┘

Init Containers vs Sidecar Containers

These are often confused — they are very different:

Init ContainerSidecar Container
When runsBefore main container startsAlongside main container
LifecycleRuns once, must complete successfullyRuns for entire pod lifetime
PurposeSetup / bootstrap tasksOngoing helper tasks
FailurePod won’t start if it failsPod restarts if it crashes
ExamplesDB migration, wait-for-service, download configLogging, proxy, metrics
spec:
# Init containers — run first, in order
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']
- name: run-migrations
image: myapp:latest
command: ['python', 'manage.py', 'migrate']
# Main + sidecar — run together after init completes
containers:
- name: api
image: myapp:latest
- name: log-shipper
image: fluentd:latest

Native Sidecar Support (Kubernetes 1.29+)

Before K8s 1.29, sidecars were just regular containers with ordering hacks. Now there is first-class sidecar support via initContainers with restartPolicy: Always.

spec:
initContainers:
# New native sidecar — starts before main app
# but keeps running alongside it
- name: log-shipper
image: fluentd:latest
restartPolicy: Always # ← this makes it a native sidecar
volumeMounts:
- name: log-volume
mountPath: /var/log/app
containers:
- name: api
image: myapp:latest

Benefits of native sidecars:

Issue (old way)Fix (native sidecar)
Sidecar starts same time as app — race conditionSidecar starts before main app
Job pods never complete (sidecar keeps running)Sidecar exits when main container exits
No guaranteed startup orderGuaranteed: init sidecars → main containers
Probe failures affect main containerSidecar lifecycle is independent

Resource Management for Sidecars

Always set resource limits — sidecars can starve your main app.

      containers:
      - name: api
        image: myapp:latest
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

      # Sidecar should be lean
      - name: log-shipper
        image: fluentd:latest
        resources:
          requests:
            memory: "50Mi"     # Keep small
            cpu: "50m"
          limits:
            memory: "100Mi"
            cpu: "100m"




Automatic Sidecar Injection (Service Mesh)

In Istio/Linkerd, sidecars are injected automatically via a Mutating Admission Webhook — you never write the sidecar spec yourself.

You apply: Kubernetes actually runs:
┌──────────────┐ ┌──────────────┬──────────────┐
│ Deployment │ Webhook │ Your App │ Envoy │
│ (1 container│─────────────▶ │ Container │ Sidecar │
│ your app) │ injects sidecar│ │ (injected) │
└──────────────┘ └──────────────┴──────────────┘
# Enable auto-injection for a namespace
kubectl label namespace production istio-injection=enabled
# Now every new pod in that namespace
# automatically gets an Envoy sidecar
kubectl apply -f deployment.yaml # ← sidecar added automatically
# Verify
kubectl get pod api-pod \
-o jsonpath='{.spec.containers[*].name}'
# Output: api istio-proxy

Sidecar Anti-Patterns

Anti-PatternProblemFix
No resource limits on sidecarSidecar can OOM-kill the nodeAlways set requests + limits
Sidecar does heavy computeStarves the main appKeep sidecars lightweight
Too many sidecars per podHigh overhead, complex debuggingMax 2-3 sidecars per pod
Sidecar holds critical business logicViolates separation of concernsBusiness logic belongs in main app
No health checks on sidecarPod looks healthy but sidecar is brokenAdd readiness probes to sidecars
Tight coupling between app and sidecarCan’t deploy independentlySidecar should be generic and reusable

When to Use a Sidecar

✅ Use sidecar when:
- Cross-cutting concern (logging, metrics, tracing)
- Needs to intercept network traffic (proxy)
- Works the same regardless of app language
- Logic is reusable across many services
❌ Don't use sidecar when:
- Logic is app-specific
- Simple enough for a library
- Performance is ultra-critical (adds overhead)
- You only have one or two services

The sidecar pattern is one of the most powerful patterns in Kubernetes — it’s what makes service meshes, log aggregation pipelines, and secret management work transparently across every service in your cluster without touching application code.

Leave a Reply