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 readinessProbeon sidecar doesn’t delay main app - it only affects Service trafficstartupProbeon sidecar does delay main app until probe passespostStartlifecycle hook also works but requires custom shell logiclivenessProbejust 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 Sidecar Containers: https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/
- Pod Lifecycle: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
- Original Kubernetes Blog Post: https://kubernetes.io/blog/2025/04/22/multi-container-pods-overview/