Skip to content
Back to blog Kubernetes Sidecar Startup Order - Making Your Main App Wait

Kubernetes Sidecar Startup Order - Making Your Main App Wait

K8sDevOps

Kubernetes Sidecar Startup Order - Making Your Main App Wait

Kubernetes 1.29 added native sidecar support. Define a container in initContainers with restartPolicy: Always and you’ve got a sidecar. It starts before your main app and terminates after.

But “starts before” doesn’t mean “is ready before”. The kubelet often launches them nearly in parallel. If your main app crashes because the sidecar isn’t serving requests yet, you’ve got a problem.

This post covers how to actually delay your main app until the sidecar is ready.

TL;DR

  • Native sidecars (initContainers + restartPolicy: Always) start before main app but don’t wait for readiness
  • readinessProbe on sidecar doesn’t delay main app - it only affects Service traffic
  • startupProbe on sidecar does delay main app until probe passes
  • postStart lifecycle hook also works but requires custom shell logic
  • livenessProbe just restarts the sidecar - doesn’t affect main app startup

PROBE/HOOK WAITS FOR SIDECAR READY? WHAT HAPPENS IF CHECK FAILS? ========== ======================== ============================ readinessProbe No Sidecar not ready, main app runs livenessProbe No Sidecar restarts, main app runs startupProbe Yes Main app doesn’t start postStart Yes (custom logic) Main app doesn’t start

The Problem

Your main app depends on a sidecar. Maybe it’s a logging agent, a service mesh proxy, or a metrics collector. If the sidecar isn’t ready when your app starts, your app crashes or errors out.

┌─────────────────────────────────────────────────────────┐
│                         POD                             │
│                                                         │
│  ┌─────────────┐         ┌─────────────┐               │
│  │   Sidecar   │◄────────│  Main App   │               │
│  │   (nginx)   │ depends │ (atlas-app) │               │
│  └─────────────┘         └─────────────┘               │
│        │                        │                       │
│        │ starts first           │ starts almost         │
│        │ (but not ready)        │ immediately after     │
│        ▼                        ▼                       │
│     RUNNING                  CRASHES                    │
│   (not ready)              (sidecar not ready)          │
└─────────────────────────────────────────────────────────┘

The “correct” fix is to make your app resilient - retry logic, graceful degradation. But sometimes you can’t change the app code. You need Kubernetes to handle the sequencing.

Native Sidecar Syntax

Quick refresher. This is how you define a native sidecar:

apiVersion: v1
kind: Pod
metadata:
  name: atlas-app
spec:
  initContainers:
    - name: nginx-sidecar
      image: nginx:1.25
      restartPolicy: Always  # <-- this makes it a sidecar
      ports:
        - containerPort: 80
  containers:
    - name: atlas-app
      image: alpine:latest
      command: ["sh", "-c", "sleep 3600"]

The sidecar goes in initContainers with restartPolicy: Always. It starts before the main container and stays running.

What Doesn’t Work: readinessProbe

You might think: “I’ll add a readinessProbe to my sidecar. Kubernetes will wait until it’s ready.”

Wrong.

initContainers:
  - name: nginx-sidecar
    image: nginx:1.25
    restartPolicy: Always
    ports:
      - containerPort: 80
    readinessProbe:
      exec:
        command: ["/bin/sh", "-c", "exit 1"]  # Always fails
      periodSeconds: 5
containers:
  - name: atlas-app
    image: alpine:latest
    command: ["sh", "-c", "sleep 3600"]

Deploy this and watch:

$ kubectl get pods -w
NAME         READY   STATUS    RESTARTS   AGE
atlas-app    1/2     Running   0          10s

Main app is running. Sidecar isn’t ready. The readinessProbe only affects whether traffic is sent to the pod via Services. It doesn’t delay container startup.

What Works: startupProbe

Add a startupProbe to your sidecar. Kubernetes won’t start the main app until this probe passes.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: atlas-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: atlas-app
  template:
    metadata:
      labels:
        app: atlas-app
    spec:
      initContainers:
        - name: nginx-sidecar
          image: nginx:1.25
          restartPolicy: Always
          ports:
            - containerPort: 80
          startupProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 5
            failureThreshold: 10
            timeoutSeconds: 5
      containers:
        - name: atlas-app
          image: alpine:latest
          command: ["sh", "-c", "echo 'Sidecar is ready!' && sleep 3600"]

Now the main app waits until nginx responds on port 80.

To prove it works, simulate a slow startup with a sleep-based probe:

startupProbe:
  exec:
    command: ["/bin/sh", "-c", "sleep 15"]
  periodSeconds: 30
  failureThreshold: 10

Watch with kubectl get pods -w - you’ll see 15 seconds before the main app starts.

Alternative: postStart Hook

The postStart lifecycle hook runs after the container starts but before Kubernetes considers it “started” for sequencing purposes.

initContainers:
  - name: nginx-sidecar
    image: nginx:1.25
    restartPolicy: Always
    ports:
      - containerPort: 80
    lifecycle:
      postStart:
        exec:
          command:
            - /bin/sh
            - -c
            - |
              echo "Waiting for nginx to be ready..."
              until curl -sf http://localhost:80; do
                echo "Still waiting..."
                sleep 2
              done
              echo "nginx is ready"

This works but requires you to write a shell script. The startupProbe approach is cleaner.

What About livenessProbe?

A livenessProbe won’t help here. It only restarts the container if the probe fails - it doesn’t affect startup order.

livenessProbe:
  exec:
    command: ["/bin/sh", "-c", "exit 1"]  # Always fails
  periodSeconds: 5

Result: sidecar keeps restarting, main app runs unaffected.

Complete Example

Here’s a production-ready example with proper probes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: atlas-app
  labels:
    app: atlas-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: atlas-app
  template:
    metadata:
      labels:
        app: atlas-app
    spec:
      initContainers:
        - name: envoy-sidecar
          image: envoyproxy/envoy:v1.28.0
          restartPolicy: Always
          ports:
            - containerPort: 9901
              name: admin
          startupProbe:
            httpGet:
              path: /ready
              port: 9901
            initialDelaySeconds: 2
            periodSeconds: 3
            failureThreshold: 20
            timeoutSeconds: 2
          readinessProbe:
            httpGet:
              path: /ready
              port: 9901
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /server_info
              port: 9901
            periodSeconds: 10
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi
      containers:
        - name: atlas-app
          image: myregistry/atlas-app:1.0.0
          ports:
            - containerPort: 8080
          env:
            - name: PROXY_URL
              value: "http://localhost:9901"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 1000m
              memory: 512Mi

Summary Table

PROBE TYPE      WHERE TO ADD    DELAYS MAIN APP?    USE CASE
==========      ============    ================    ========
startupProbe    Sidecar         Yes                 Wait for sidecar ready
readinessProbe  Sidecar         No                  Control Service traffic
livenessProbe   Sidecar         No                  Restart unhealthy sidecar
postStart       Sidecar         Yes                 Custom wait logic

References

======================================== Kubernetes + Sidecars + startupProbe

Control your startup order. No code changes.

Found this helpful?

Comments