Istio
Kubernetes
Containers
Pod Management
Service Mesh

Starting a container/pod after running the istio-proxy

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In Istio-enabled Kubernetes clusters, the istio-proxy (Envoy) sidecar starts alongside your application container. If your application tries to make network calls before the sidecar is ready, those calls fail because the iptables rules redirect traffic to the not-yet-ready proxy. The solution depends on your Istio version: Istio 1.7+ supports holdApplicationUntilProxyStarts, Istio 1.18+ uses native sidecar containers (Kubernetes 1.28+), and older versions require init container workarounds.

The Problem

yaml
1# Application container starts and immediately calls an external API
2# But istio-proxy is not ready yet — connection refused
3apiVersion: v1
4kind: Pod
5metadata:
6  name: my-app
7spec:
8  containers:
9    - name: my-app
10      image: my-app:latest
11      # This fails if istio-proxy isn't ready yet
12      # because iptables rules redirect traffic to the proxy

Istio injects iptables rules via the istio-init container that redirect all traffic through the Envoy sidecar. If the sidecar is not listening when your application starts, outbound connections are refused.

Fix 1: holdApplicationUntilProxyStarts (Istio 1.7+)

yaml
1# Global setting in Istio ConfigMap
2apiVersion: install.istio.io/v1alpha1
3kind: IstioOperator
4spec:
5  meshConfig:
6    defaultConfig:
7      holdApplicationUntilProxyStarts: true
yaml
1# Per-pod annotation
2apiVersion: v1
3kind: Pod
4metadata:
5  name: my-app
6  annotations:
7    proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}'
8spec:
9  containers:
10    - name: my-app
11      image: my-app:latest

This setting makes the sidecar injector add a postStart lifecycle hook that blocks the application container from starting until the Envoy proxy is ready to accept traffic.

Fix 2: Native Sidecar Containers (Istio 1.18+ / Kubernetes 1.28+)

yaml
1# With Kubernetes 1.28+ SidecarContainers feature gate
2# Istio uses the native sidecar support
3apiVersion: v1
4kind: Pod
5metadata:
6  name: my-app
7  labels:
8    sidecar.istio.io/inject: "true"
9spec:
10  # Istio injects as an init container with restartPolicy: Always
11  # This guarantees the proxy starts before the app container
12  containers:
13    - name: my-app
14      image: my-app:latest

Kubernetes 1.28 introduced native sidecar containers (init containers with restartPolicy: Always). Istio 1.18+ uses this feature to guarantee the proxy is running before application containers start.

Fix 3: Init Container Workaround (Older Istio)

yaml
1apiVersion: v1
2kind: Pod
3metadata:
4  name: my-app
5  annotations:
6    sidecar.istio.io/inject: "true"
7spec:
8  initContainers:
9    - name: wait-for-proxy
10      image: curlimages/curl:latest
11      command:
12        - sh
13        - -c
14        - |
15          until curl -fsI http://localhost:15021/healthz/ready; do
16            echo "Waiting for Istio proxy..."
17            sleep 1
18          done
19          echo "Istio proxy is ready"
20  containers:
21    - name: my-app
22      image: my-app:latest

This init container polls the Envoy health endpoint (localhost:15021/healthz/ready) before allowing the application container to start. However, this only works if the proxy is started before init containers, which depends on injection order.

Fix 4: Application-Level Retry

python
1# Add retry logic in your application for startup connections
2import time
3import requests
4from requests.adapters import HTTPAdapter
5from urllib3.util.retry import Retry
6
7session = requests.Session()
8retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
9session.mount('http://', HTTPAdapter(max_retries=retries))
10session.mount('https://', HTTPAdapter(max_retries=retries))
11
12# This retries automatically if the proxy isn't ready yet
13response = session.get('https://api.example.com/data')

Adding retry logic makes your application resilient regardless of sidecar timing. This is a good practice even outside Istio because network calls can fail for many reasons.

Fix 5: Sidecar Injection Order

yaml
1# Ensure istio-proxy starts first via container ordering
2apiVersion: v1
3kind: Pod
4metadata:
5  name: my-app
6  annotations:
7    # Customize the proxy startup
8    sidecar.istio.io/proxyMemory: "128Mi"
9    sidecar.istio.io/proxyCPU: "100m"
10spec:
11  containers:
12    - name: istio-proxy  # Injected automatically
13    - name: my-app
14      image: my-app:latest
15      lifecycle:
16        postStart:
17          exec:
18            command:
19              - sh
20              - -c
21              - "sleep 2"  # Simple delay to let proxy initialize

A postStart hook with a short delay is a simple workaround, but it is fragile — the required delay depends on cluster load and proxy startup time.

Handling Proxy Shutdown (Exit Order)

yaml
1# Ensure app container exits before the proxy
2apiVersion: v1
3kind: Pod
4metadata:
5  annotations:
6    # Istio 1.12+: quit the proxy when the app container exits
7    proxy.istio.io/config: '{"terminationDrainDuration": "5s"}'
8spec:
9  containers:
10    - name: my-app
11      image: my-app:latest
12      # For Jobs: signal the proxy to quit after the app finishes
13      lifecycle:
14        preStop:
15          exec:
16            command:
17              - sh
18              - -c
19              - "curl -X POST http://localhost:15020/quitquitquit"

For Kubernetes Jobs and CronJobs, the istio-proxy keeps the pod running after the application container exits. Use the /quitquitquit endpoint to tell the proxy to shut down.

Common Pitfalls

  • Not enabling holdApplicationUntilProxyStarts: Without this setting, application containers and the sidecar start simultaneously. Fast-starting apps that immediately make network calls will fail on the first request.
  • Init container cannot reach the proxy: Init containers run before sidecar injection in most configurations. The wait-for-proxy init container pattern only works if the proxy is already injected and running, which requires Kubernetes 1.28+ native sidecars.
  • Jobs stuck in Running due to proxy: Kubernetes Jobs complete when all containers exit. The istio-proxy does not exit on its own, leaving the Job in Running state. Use quitquitquit endpoint or set ISTIO_QUIT_API to true.
  • Health checks failing during startup: If your readiness probe hits an endpoint that goes through the sidecar, it fails while the proxy is starting. Use direct container ports or holdApplicationUntilProxyStarts.
  • Hardcoded sleep as a workaround: sleep 5 in a postStart hook is unreliable. Proxy startup time varies with cluster load. Use the health endpoint poll or holdApplicationUntilProxyStarts for deterministic behavior.

Summary

  • Use holdApplicationUntilProxyStarts: true (Istio 1.7+) for the simplest fix
  • Kubernetes 1.28+ with Istio 1.18+ uses native sidecar containers that guarantee startup order
  • For older versions, poll localhost:15021/healthz/ready in an init container or application retry logic
  • For Jobs and CronJobs, call localhost:15020/quitquitquit to stop the proxy after the app exits
  • Add application-level retry logic as defense-in-depth regardless of sidecar configuration

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.