πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Deployments intermediate ⏱ 20 minutes K8s 1.28+

Native Sidecar Containers in K8s: Complete ...

Use native sidecar containers in Kubernetes v1.33+. InitContainer restartPolicy Always, lifecycle ordering, logging sidecars, service mesh.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: Native sidecar containers (stable in Kubernetes v1.33) are init containers with restartPolicy: Always. They start before regular containers, run alongside them for the pod’s lifetime, and shut down after regular containers exit. This fixes long-standing issues with Job completion (istio-proxy blocking), log collection, and startup ordering.

The Problem

Before v1.33, sidecars were just regular containers β€” Kubernetes didn’t know they were β€œsidecars.” This caused problems: Jobs never completed because the sidecar kept running, sidecar containers started simultaneously with the main app (race conditions), and there was no way to say β€œstart the proxy first, then the app.”

flowchart TB
    subgraph OLD["Legacy Sidecar (pre-v1.33)"]
        direction LR
        A1["Container A<br/>(app)"] ---|"Same lifecycle"| A2["Container B<br/>(sidecar)"]
        A3["❌ Job never completes<br/>❌ No startup ordering<br/>❌ Race conditions"]
    end
    
    subgraph NEW["Native Sidecar (v1.33+)"]
        direction LR
        B1["Init Sidecar<br/>starts FIRST"] --> B2["Main Container<br/>starts AFTER"] --> B3["Init Sidecar<br/>stops LAST"]
        B4["βœ… Job completes when main exits<br/>βœ… Guaranteed startup order<br/>βœ… Graceful shutdown order"]
    end

The Solution

Basic Native Sidecar

apiVersion: v1
kind: Pod
metadata:
  name: app-with-sidecar
spec:
  initContainers:
    # This is the native sidecar β€” restartPolicy: Always makes it persist
    - name: log-collector
      image: fluent/fluent-bit:3.1
      restartPolicy: Always          # ← THIS makes it a native sidecar
      volumeMounts:
        - name: logs
          mountPath: /var/log/app
      resources:
        requests:
          cpu: "50m"
          memory: "64Mi"

  containers:
    # Main application β€” starts AFTER log-collector is running
    - name: app
      image: myorg/web-app:v2.0
      volumeMounts:
        - name: logs
          mountPath: /var/log/app

  volumes:
    - name: logs
      emptyDir: {}

Service Mesh Sidecar (Istio/Envoy)

apiVersion: v1
kind: Pod
metadata:
  name: app-with-istio
spec:
  initContainers:
    # Envoy proxy starts first β€” guaranteed ready before app
    - name: istio-proxy
      image: docker.io/istio/proxyv2:1.23.0
      restartPolicy: Always
      ports:
        - containerPort: 15090       # Prometheus metrics
      readinessProbe:
        httpGet:
          path: /healthz/ready
          port: 15021
        initialDelaySeconds: 1
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
        limits:
          cpu: "2"
          memory: "1Gi"

  containers:
    - name: app
      image: myorg/api-server:v3.0
      # App starts ONLY after istio-proxy readiness probe passes
      # No more "connection refused" on startup!
      ports:
        - containerPort: 8080

Job with Native Sidecar (Fixed!)

# Before v1.33: Job never completed because sidecar kept running
# After v1.33: Sidecar auto-terminates when main container exits
apiVersion: batch/v1
kind: Job
metadata:
  name: data-migration
spec:
  template:
    spec:
      initContainers:
        - name: cloud-sql-proxy
          image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.12.0
          restartPolicy: Always      # Sidecar: runs alongside main
          args:
            - "myproject:us-central1:mydb"
            - "--port=5432"
          readinessProbe:
            tcpSocket:
              port: 5432
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"

      containers:
        - name: migration
          image: myorg/db-migrator:v1.0
          command: ["migrate", "--source=file:///migrations", "--database=postgres://localhost:5432/mydb"]
      
      restartPolicy: Never
  # Job completes when migration exits!
  # cloud-sql-proxy stops automatically after.

Multiple Sidecars with Ordering

apiVersion: v1
kind: Pod
metadata:
  name: ordered-sidecars
spec:
  initContainers:
    # Sidecar 1: starts first
    - name: vault-agent
      image: hashicorp/vault:1.17
      restartPolicy: Always
      args: ["agent", "-config=/etc/vault/agent.hcl"]
      readinessProbe:
        httpGet:
          path: /v1/sys/health
          port: 8200

    # Regular init: runs after vault-agent is ready, before sidecars below
    - name: db-schema-check
      image: myorg/schema-checker:v1.0
      command: ["check", "--wait"]

    # Sidecar 2: starts after schema check passes
    - name: envoy
      image: envoyproxy/envoy:v1.31
      restartPolicy: Always
      readinessProbe:
        httpGet:
          path: /ready
          port: 9901

  containers:
    # Main app: starts after ALL init containers (including sidecars) are ready
    - name: app
      image: myorg/app:v3.0

Lifecycle: Startup and Shutdown Order

STARTUP ORDER (left to right):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ vault-agent  │──▢│ schema-check │──▢│   envoy     │──▢│   app    β”‚
β”‚ (sidecar)    β”‚   β”‚ (init, exits)β”‚   β”‚ (sidecar)   β”‚   β”‚ (main)   β”‚
β”‚ starts first β”‚   β”‚ runs & exits β”‚   β”‚ starts next β”‚   β”‚ last     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

SHUTDOWN ORDER (right to left):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   app    │──▢│   envoy     │──▢│ vault-agent  β”‚
β”‚ stops 1stβ”‚   β”‚ stops 2nd   β”‚   β”‚ stops last   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Migration from Legacy Sidecar

# BEFORE (legacy sidecar β€” broken Job completion)
containers:
  - name: app
    image: myorg/app:v2.0
  - name: fluent-bit                    # Regular container
    image: fluent/fluent-bit:3.1

# AFTER (native sidecar β€” correct lifecycle)
initContainers:
  - name: fluent-bit
    image: fluent/fluent-bit:3.1
    restartPolicy: Always               # Move to initContainers + add this
containers:
  - name: app
    image: myorg/app:v2.0

Common Issues

IssueCauseFix
Sidecar not persistingMissing restartPolicy: AlwaysAdd to initContainer spec
App starts before sidecar readyNo readiness probe on sidecarAdd readiness probe to sidecar
Old K8s versionFeature requires v1.33+Upgrade cluster or use legacy pattern
Sidecar not in initContainersPut in containers insteadMove to initContainers with restartPolicy: Always
Resource limits too lowSidecar OOMKilledIncrease sidecar memory limits

Best Practices

  • Always add readiness probes to sidecars β€” ensures main container waits
  • Use native sidecars for Jobs β€” finally fixes the β€œsidecar blocks Job completion” problem
  • Order sidecars intentionally β€” init container ordering controls startup sequence
  • Set resource requests on sidecars β€” they compete with main container for pod resources
  • Migrate progressively β€” test native sidecars in staging before fleet-wide rollout
  • Check v1.33+ requirement β€” native sidecars are stable in v1.33, beta in v1.29

Key Takeaways

  • Native sidecars = init containers with restartPolicy: Always
  • They start before main containers and stop after β€” guaranteed lifecycle ordering
  • Fixes Job completion: sidecar exits when main container is done
  • Startup ordering: vault-agent β†’ envoy β†’ app (no more race conditions)
  • Stable in Kubernetes v1.33 (April 2025) β€” safe for production
  • Migration is simple: move container to initContainers, add restartPolicy: Always
#sidecar #init-containers #service-mesh #logging #kubernetes-features
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens