Kubernetes Native Sidecar Containers: Lifecycle, Startup Ordering and Job Shutdown
For roughly a decade, the sidecar was a pattern Kubernetes did not understand. A proxy, a log shipper and your application were three “equal” containers in one Pod, and the kubelet gave you no way to say “start the proxy first” or “stop it last.” The results were familiar to anyone who has run a service mesh: applications that boot before the proxy and fail their first outbound call, Jobs that finish their work and then hang forever because a log shipper never exits, and traffic dropped during shutdown because the proxy died before the app did.
Kubernetes native sidecar containers fix this at the API level. A sidecar is now an init container with restartPolicy: Always, and the kubelet gives it a defined startup order, independent restarts, probe support and a reverse-order shutdown. The feature, tracked as KEP-753, is stable since Kubernetes v1.33 and its feature gate can no longer be turned off.
This post explains exactly how the lifecycle works, what the kubelet does at each step, where the guarantees stop, and how to migrate a legacy sidecar safely, with runnable YAML throughout.
What this covers: the history and the problem, the startup state machine, the shutdown sequence, Job completion behavior, restart and probe semantics, resource accounting, failure modes, and a migration checklist.
Context and Background
A Pod is a group of containers sharing a network namespace and, optionally, volumes. The “sidecar” idea, extra containers that extend a main container, was described in a 2015 Kubernetes blog post on composite containers and became the default way to add networking, logging and security behavior without touching application code. Service meshes such as Istio, secrets agents, log forwarders, database proxies and certificate refreshers all use it.
The weakness was that, to the kubelet, every non-init container was the same. Container start order inside the containers list was not a guarantee; containers were started in sequence but nothing waited for the previous one to become ready. Termination was parallel: on Pod deletion, every container received SIGTERM at roughly the same moment. And a container that never exits keeps a Pod alive, which is a disaster for batch workloads.
The Kubernetes blog that introduced the feature in v1.28 listed the pre-existing options and their flaws. If the sidecar’s lifetime was shorter than the Pod’s, you could use a regular init container, which gives ordering but must exit before anything else starts. If the sidecar’s lifetime equaled the Pod’s, you used a main container, which gave no startup control and could block Pod termination after the workload finished. Native sidecars target exactly the second case, and add ordering and non-blocking termination.
Istio’s own write-up of the feature is blunt about the pain. It names the application that starts before the proxy and cannot reach the network as the most-upvoted Istio GitHub issue, and the Job that never completes because the proxy keeps running as another top issue. It also notes that the two related Kubernetes issues were among the most-commented in the project’s history. If you run a mesh, the sidecar-less alternative is also worth knowing; our Cilium service mesh deep dive covers the architecture that removes the per-Pod proxy altogether.
Version timeline
The Kubernetes documentation states that the feature was first available in v1.28 (alpha), that the SidecarContainers gate moved to beta and on-by-default in v1.29, and that it graduated to stable in v1.33, after which the gate is locked and any explicit setting is ignored without error. The v1.33 release announcement adds two details that matter for operators: sidecars can use startup, readiness and liveness probes, and their out-of-memory (OOM) score adjustment is aligned with primary containers so the kernel is not more eager to kill them under memory pressure.
If you are on any supported 2026 release, you have the feature. The remaining question is not “can I use it” but “have my Helm charts, mutating webhooks and operators caught up.” We return to that in the migration section.
The Native Sidecar Model: A Restartable Init Container
A native sidecar is an entry in spec.initContainers with restartPolicy: Always. Because it lives in the init list, it inherits strict sequential startup. Because of the restart policy, it keeps running after it starts, restarts on exit, supports probes, and is shut down after the main containers, in reverse order.

Figure 1: Startup ordering in a Pod with a regular init container, two native sidecars and an application container. Each step waits for the previous container to be “started.”
Figure 1 shows the sequence the kubelet walks. The idea is simple enough to state in one sentence: the Pod spec already had an ordered list that the kubelet processes one entry at a time, and the design reuses it. A regular init container must run to completion. A restartable init container only has to reach the started state, then the kubelet moves on to the next entry while the sidecar keeps running.
What “started” means
The documentation defines the gate precisely. After a sidecar-style init container is running and the kubelet has set its started status to true, the kubelet starts the next init container in the ordered list. That status becomes true in one of two ways: there is a process running in the container and no startup probe is defined, or the container’s startupProbe succeeded.
This distinction is the most important operational detail in the whole feature. Without a startup probe, “started” means only that the container process exists. A proxy that has launched its binary but has not yet loaded its configuration or opened its listeners counts as started. If you want the application to wait until the proxy can actually forward traffic, you must give the sidecar a startupProbe. A readinessProbe alone does not delay the next container; it feeds Pod readiness, which is a different gate.
The spec, minimal and complete
The following Pod is a valid native-sidecar example you can apply to any cluster at v1.29 or later. The proxy starts first, the init step then runs with the proxy available, and the application starts last.
apiVersion: v1
kind: Pod
metadata:
name: sidecar-demo
spec:
initContainers:
- name: net-proxy # sidecar: restartPolicy Always
image: registry.example.com/proxy:1.0
restartPolicy: Always
startupProbe:
httpGet: { path: /ready, port: 15021 }
periodSeconds: 1
failureThreshold: 30
readinessProbe:
httpGet: { path: /ready, port: 15021 }
- name: fetch-config # regular init container: runs to completion
image: registry.example.com/fetch:1.0
command: ["sh", "-c", "curl -fsS http://localhost:8080/config > /work/config.json"]
volumeMounts: [{ name: work, mountPath: /work }]
containers:
- name: app
image: registry.example.com/app:2.3
volumeMounts: [{ name: work, mountPath: /work }]
volumes:
- name: work
emptyDir: {}
Three properties of this manifest are worth noticing. The image names are placeholders. The fetch-config init container can reach the network through the proxy, which was impossible with a main-container sidecar because init containers ran before it existed. And the only field that makes net-proxy a sidecar is restartPolicy: Always; the field is optional, and Always is its only value for the original feature.
Why reuse init containers instead of a new field
My reading of the design, offered as analysis rather than a quote from the KEP: reusing initContainers meant the ordering semantics, status reporting and kubectl logs -c path already worked, and consumers of the Pod API did not have to learn a third container kind. The cost is the slightly surprising fact that your long-running proxy appears under initContainers. That is the source of most first-week confusion, and it is worth explaining to your team before the first rollout.
What the field actually changes
The v1.28 blog lists four concrete effects of restartPolicy: Always on an init container. The container restarts if it exits. Subsequent init containers start right after the startup probe succeeds, rather than waiting for the sidecar to exit. The Pod’s resource calculation changes because the sidecar’s requests are now added to the main containers’ sum. And Pod termination depends only on the main containers: a running sidecar does not hold the Pod open.
The lifecycle documentation adds that a sidecar ignores the Pod-level restartPolicy and uses its own container-level value, which is always Always. A Pod with restartPolicy: OnFailure therefore restarts its main container only on a non-zero exit, while the sidecar restarts regardless of exit code. The kubelet restarts containers with an exponential backoff of 10 seconds, 20 seconds, 40 seconds and so on, capped at 300 seconds, and resets the timer after a container has run cleanly for 10 minutes. The documentation states that the sidecar follows the same exponential backoff behavior.
Pod readiness and probes
Sidecars can use all three probe types, a capability regular init containers lack. The KEP states that readiness probes of sidecars contribute to the whole Pod’s readiness. That means a sidecar whose readiness probe fails removes the Pod’s endpoints from Services, which is exactly what you want for a proxy that has lost its control-plane connection, and exactly what you do not want for a log shipper whose backend is down. Choose readiness probes deliberately: use them for sidecars that sit on the request path, and omit them for ones that do not.
Lifecycle hooks are also allowed. postStart and preStop handlers, unavailable to regular init containers, are enabled for sidecars. The preStop hook plays a central role in shutdown, which is the next section’s subject.
Shutdown: Reverse Order, After the Main Containers
When a Pod is deleted, the kubelet runs preStop hooks and sends SIGTERM to the main containers first. It delays SIGTERM to sidecars until the last main container has fully terminated, then stops the sidecars one at a time in the reverse of their order in the Pod spec.

Figure 2: Pod termination with two native sidecars. The sidecar declared first is the last to receive SIGTERM.
The official Pod lifecycle page says this in two sentences: the kubelet delays sending the TERM signal to sidecar containers until the last main container has fully terminated, and sidecars are terminated in the reverse of the order they are defined in the Pod spec. It adds the consequence that a slow main container also delays sidecar termination, because the sidecar’s clock does not start until the app is gone.
The termination algorithm in detail
The KEP contains the clearest description of the algorithm, in pseudocode form. Its steps, paraphrased:
- Sidecars that have a
preStophook have that hook executed when the Pod begins terminating, at the same time as the regular containers’ hooks. This starts the Pod’s termination grace period countdown. - Once the last primary container terminates, the most recently started sidecar receives SIGTERM.
- When it terminates, the next sidecar in reverse order receives SIGTERM, and so on.
- If the grace period expires first, every remaining container receives SIGTERM, followed by a fixed 2-second grace period and then SIGKILL.
Two details deserve emphasis. The first is that step 1 means a sidecar’s preStop hook runs concurrently with the application’s shutdown, not after it. A hook that sleeps for 10 seconds consumes 10 seconds of the shared grace budget while the app is also draining. The second is that the KEP says plainly that ordering and graceful termination can only be guaranteed if the preStop hook completes within the Pod’s termination grace period.
A single clock for everything
terminationGracePeriodSeconds is a Pod-wide budget, 30 seconds by default. It is not per container and it is not reset when the app finishes. Consider a Pod with a 30-second budget where the app needs 25 seconds to drain connections. The sidecar proxy then has about 5 seconds to flush its own state. If the proxy needs 10, it is hit with SIGTERM at the deadline and then SIGKILL 2 seconds later. The numbers here are illustrative arithmetic, not measurements, but the shape of the problem is real: shutdown time composes serially.
The practical rule is that the grace period must cover the sum of the app’s drain time, plus each sidecar’s flush time, plus margin. Teams that previously ran parallel termination often had the grace period tuned to the slowest single container. After migration, it has to be revisited.
The documentation also states a consequence for exit codes. When other containers consume the whole grace period, sidecars get SIGTERM followed by SIGKILL without time to exit gracefully, so non-zero exit codes from sidecars at Pod termination are normal and should generally be ignored by external tooling. If your alerting fires on any non-zero container exit, add an exclusion for sidecars or you will page people for routine deletions.
Restarts during shutdown
The KEP notes that sidecars continue to be restarted until they enter the Terminated state through the SIGTERM they receive. This ensures a crashing sidecar is revived while the main container still needs it. A separate KEP, number 4438, was referenced for restarting sidecars even during Pod termination more generally. I could not confirm that enhancement’s current stage, so treat that behavior as unverified and test it on your own version before depending on it.
Making the guarantee real for a mesh proxy
Ordering guarantees when SIGTERM arrives; it does not make the proxy behave well on receipt. A typical mesh proxy stops accepting new connections on SIGTERM and drains existing ones for a configured period. The app’s outbound calls during its own shutdown still flow through the sidecar, which stays fully alive until the app exits. That closes the classic failure where the proxy died first and the app’s final in-flight requests, its flush of buffered writes, or its deregistration call failed. It does not close the inbound-side race, where the Service endpoint removal propagates to kube-proxy or the Gateway layer slower than the Pod stops accepting traffic. That race is independent of sidecars and still calls for a short preStop sleep or readiness-based draining on the app.
If you front applications with an ingress controller, the same endpoint-propagation logic applies one layer out; our ingress-nginx retirement and Gateway API migration playbook discusses how traffic-path changes interact with Pod lifecycle during rollouts.
Jobs, CronJobs and the Completion Problem
A native sidecar does not block Pod completion. When all regular containers finish, the kubelet shuts the sidecars down, the Pod reaches a terminal phase, and the Job controller counts it complete. This works with no extra configuration and with restartPolicy: Never or OnFailure.

Figure 3: Legacy versus native handling when a Job container exits. The legacy sidecar keeps the Pod Running; the native sidecar is terminated in reverse order and the Pod completes.
This was the single most-requested behavior behind the whole KEP. The KEP’s motivation section describes a Job with two containers: one does the processing and the other is a helper. When processing finishes, the Pod does not terminate until the sidecar exits, which it never does by design. The Kubernetes documentation now includes a Job example in which the log shipper is declared under initContainers with restartPolicy: Always and notes that the sidecar in each Pod does not prevent the Job from completing after the main container finishes.
The legacy workarounds, and why they were brittle
Before native sidecars, teams used one of three tricks. The first was a shared file or signal: the main container writes a “done” marker to an emptyDir and a wrapper script in the sidecar exits when it sees it. Tools like kubexit, which the KEP cites, packaged this idea. The second was to give the sidecar an activeDeadlineSeconds ceiling. The third was a controller or operator that watched Pods and deleted them when the main container finished.
All three share a weakness: the sidecar image must cooperate, often through a wrapper entrypoint or a modified build. With third-party sidecars such as a mesh proxy or a cloud SQL proxy, you either patched the image or ran an API call such as the proxy’s quit endpoint in your job’s last step. Failures in that last step left Pods hanging and Jobs unfinished. Native sidecars remove the cooperation requirement entirely, because the kubelet, not the sidecar, decides when the Pod is done.
Failure semantics worth testing
Native sidecars change how Jobs behave in a few edge cases, and the KEP and docs give enough to reason about them:
- Job container fails with
restartPolicy: Never. The Pod becomes failed, sidecars are shut down, and the Job controller creates a replacement Pod subject tobackoffLimit. I have not verified how sidecar restarts are counted against Job retry limits, so test that on your version. - Sidecar crashes while the main container runs. The kubelet restarts it with exponential backoff, even if the Pod has
restartPolicy: Never, because the container-level policy overrides the Pod-level one for sidecars. Be careful: a sidecar that crash-loops will not fail the Job by itself. - Sidecar is required for correctness. If the job container cannot make progress without the sidecar, the job will stall rather than fail fast. Pair the sidecar with a readiness or liveness probe, and have the main container use a client timeout.
- Sidecar OOM-killed. The alignment of OOM score adjustments with regular containers means the sidecar is no more likely than the main container to be chosen. The KEP raised exactly this problem because legacy sidecars with small requests were killed first.
Indexed Jobs, parallelism and cost
Because each Pod now completes cleanly, the usual Job accounting works: completions, parallelism and ttlSecondsAfterFinished behave as for sidecar-free Jobs. For fleets of short Jobs, such as an ML batch scoring run with a proxy for model-registry access, this removes both wasted node time from hung Pods and the operational toil of hunting them. If a hanging Pod previously held a GPU node for an extra hour per failure, the saving is easy to estimate from your own Job history; I won’t invent a figure here.
Resource Accounting, QoS and Scheduling
For scheduling, a Pod’s effective request is the sum of Pod overhead and the larger of two values: the sum of all app and sidecar container requests, or the effective init request. Sidecars therefore add to the steady-state total, while regular init containers only matter if they are the peak.

Figure 4: How the scheduler computes the effective request for a Pod with regular init containers, sidecars and app containers.
The Kubernetes documentation spells out the rules. The highest request or limit across the regular init containers is the effective init request. The Pod’s effective value is Pod overhead plus the higher of (a) the sum of all non-init containers, which here means app and sidecar containers, and (b) the effective init value. Scheduling, quota and limit enforcement use these effective numbers, and on Linux the Pod-level cgroup uses the same values. The QoS class is computed across init, sidecar and app containers together.
A worked example, with illustrative numbers
Take a Pod with one regular init container requesting 500m CPU, a proxy sidecar requesting 100m, a log-shipper sidecar requesting 50m, and an app requesting 400m. These figures are made up for illustration. Steady state is 100 + 50 + 400 = 550m. The init peak is 500m. The Pod’s effective request is the larger, 550m.
Now suppose the init container requested 800m instead, perhaps for a heavy schema migration. The effective request becomes 800m, even though the Pod will only use 550m after initialization. The documentation notes this directly: init containers can reserve resources for initialization that are not used during the life of the Pod.
There is a subtle point for sidecar-heavy Pods. Under the legacy model, a proxy that ran as a main container was already summed with the app, so migration to a native sidecar does not usually change the steady-state number. What can change is Pod-level QoS and the interplay with init containers: a regular init container that runs after a sidecar starts is accounted for alongside it in the init phase, so a large init container can now overlap with sidecar usage in real life even though the formula treats them separately. Watch peak node usage during init if your init steps are heavy.
Why QoS and memory behavior matter for proxies
A sidecar with small requests and no limit is a Burstable container; set equal requests and limits on every container and the Pod becomes Guaranteed. For a mesh proxy, memory is the usual pressure point: the proxy’s memory grows with the number of services and routes it knows. If you are on the new Pod-level resource model, our post on Kubernetes 1.37 Memory QoS and pod-level resource managers discusses how pod-level budgets change the sizing exercise for multi-container Pods. The v1.28 alpha blog noted that the CPU, memory, device and topology managers were unaware of sidecar lifetimes at that stage, which was one of its listed known issues to fix before beta; by stable the feature is considered complete, but if you use static CPU pinning with sidecars, validate the allocation on your own nodes.
OOM scores
The KEP states the intent: the OOM adjustment for sidecars matches or falls below that of regular containers in the Pod, so the node’s OOM killer does not preferentially pick the sidecar. The v1.33 release notes repeat this property. In practice, a sidecar still dies if it exceeds its own memory limit, which is a cgroup OOM and independent of node-level scoring. Set the limit with headroom, and alert on OOMKilled termination reasons for sidecar containers separately from the app.
Restarts, Probes and the Interaction With Everything Else
A sidecar’s lifecycle has three independent loops: startup (the one-time gate for ordering), readiness (contributes to Pod readiness) and liveness (restarts the container). Getting them right is mostly about deciding which loop each of your sidecars needs.
A probe-selection table
| Sidecar type | Startup probe | Readiness probe | Liveness probe | Notes |
|---|---|---|---|---|
| Mesh or network proxy on the request path | Yes, so the app waits for listeners | Yes, to drop the Pod from endpoints if the proxy is unhealthy | Cautious: restarts drop connections | Order it first in initContainers |
| Log shipper or metrics agent | Optional | No, a backend outage should not unready the app | Optional | Exits after the app thanks to reverse ordering |
| Secrets or certificate refresher | Yes, if the app reads files at boot | Rarely | Optional | Often paired with a shared emptyDir |
| Cloud database proxy | Yes | Yes if the app cannot work without it | Optional | Check its quit and drain behavior |
The table is advice, not a quotation from documentation. The underlying facts, that sidecars support all three probe types and that their readiness feeds Pod readiness, are from the KEP and release notes.
Ordering between several sidecars
Ordering within the initContainers list is strictly sequential. If sidecar B depends on sidecar A (for example, a metrics agent that sends through the proxy), declare A first and give it a startup probe. Shutdown is the mirror image: B is stopped before A, so A is available while B flushes. This symmetric property is the main reason to think about order at all. If two sidecars are independent, order barely matters, but still give each a startup probe so a stuck one is visible as a stuck Pod initialization rather than a mysterious half-ready Pod.
A subtle consequence: a sidecar whose startup probe never succeeds blocks all later init containers and all app containers. The Pod stays Pending in an init state. The adoption tutorial describes this as easy to detect because the Pod is stuck on initialization, though the cause is often unclear.
Per-container restart rules in newer releases
The current Pod lifecycle documentation describes a further feature, ContainerRestartRules, listed as beta since v1.35 and enabled by default. It lets you set restartPolicy and restartPolicyRules on individual containers, using exit codes as the condition and Restart as the action. The documentation says this applies to app containers and regular init containers, and that a native sidecar keeps its container-level restartPolicy of Always. So restart rules complement sidecars rather than replace them. The same page also lists an alpha capability for restarting all containers in a Pod; as alpha, it is off by default and not something to design around yet.
Changing the image of a sidecar
Per the documentation, changing a sidecar’s image does not restart the Pod; it triggers a container restart. Combined with independent lifecycles, this makes in-place sidecar upgrades feasible for patch-level changes without rescheduling the workload. Be aware that a Deployment controller still sees the template change and performs a normal rolling update; the in-place behavior applies to direct Pod updates, where only a small set of fields, container images included, are mutable.
Migrating a Legacy Sidecar Safely
Migration is mostly mechanical, but the checks around it are where outages are avoided. The v1.28 blog recommends exactly the basic move: take a sidecar configured as a main container, move it to initContainers, and add restartPolicy: Always. It adds that in many cases the sidecar should work as before, with the benefit of a defined startup order and no longer prolonging Pod lifetime.
Before and after
# BEFORE: legacy sidecar, parallel start, parallel stop, blocks Job completion
spec:
containers:
- name: app
image: registry.example.com/app:2.3
- name: proxy
image: registry.example.com/proxy:1.0
---
# AFTER: native sidecar
spec:
initContainers:
- name: proxy
image: registry.example.com/proxy:1.0
restartPolicy: Always
startupProbe:
httpGet: { path: /ready, port: 15021 }
periodSeconds: 1
failureThreshold: 30
containers:
- name: app
image: registry.example.com/app:2.3
Compatibility checks
The adoption tutorial lists the failure modes. First, both the API servers and the nodes must be at v1.29 or later; nodes on older versions break the feature. You can verify gate state through the kubernetes_feature_enabled metric on the API server and on each kubelet. Second, mutating webhooks and operators built against an older Kubernetes API client may strip the unknown restartPolicy field when they do a full-object update, leaving a sidecar that behaves as a plain init container and blocks startup forever. The check is to run kubectl describe pod on a Pod that went through admission and confirm the field survived. The fix is for the tool to be rebuilt against a v1.28+ client or to use patch-based mutation.
Third, the tutorial warns that webhooks may mutate only some Pods under certain conditions, so sidecars may work for some Pods and fail for others. Test with the actual admission path, not with a hand-written manifest.
Mesh injection
Istio implemented support behind a pilot environment flag, ENABLE_NATIVE_SIDECARS, in the 2023 post; in that demo the injected istio-proxy appears as an init container with restartPolicy: Always, and later containers do not start until the proxy is ready. I could not confirm from the sources I retrieved whether newer Istio releases enable it by default, so check your mesh’s current documentation. If your mesh is sidecarless, as with Cilium or Istio’s ambient mode, this feature matters less for the mesh itself but still applies to your log, secrets and database sidecars.
Rollout plan
Migrate one workload class at a time. Start with Jobs, where the benefit is largest and the risk lowest. Then move request-path sidecars with startup probes, in a canary namespace, watching Pod startup time, Initialized condition delays and restart counts. Keep the old manifest in version control for rollback. Because the field is nullable and additive, rolling back is a manifest change, not a cluster change.
Trade-offs, Gotchas, and What Goes Wrong
Native sidecars solve ordering and completion, but they are not a universal fix. These are the failure modes I would test for before calling a migration done.
“Started” is not “ready.” Without a startup probe, the next container launches as soon as the sidecar’s process exists. Many migrations “worked in staging” because the proxy happened to bind its port fast, then failed in production under CPU pressure. Always add a startup probe to any sidecar the app depends on at boot.
A stuck sidecar blocks the whole Pod. Because sidecars are init containers, a sidecar that never becomes started holds back every later container. This is safer than the old silent race, but it turns a flaky dependency, such as a proxy waiting for a control plane that is down, into a Pod that never starts. Give startup probes a sensible failureThreshold, and alert on Pods stuck in initialization.
Serial shutdown eats the grace period. Shutdown is sequential, so total time is app drain plus each sidecar’s stop time. If the Pod’s grace period was tuned for parallel termination, sidecars will be hit with SIGKILL. Review terminationGracePeriodSeconds for every migrated workload.
Noisy exit codes. The documentation says non-zero sidecar exit codes during termination are normal. Dashboards counting failed containers will show false alarms until you filter them.
Crash loops are quiet. A sidecar restarts with exponential backoff capped at five minutes, even inside a restartPolicy: Never Pod. A main container can keep working while a broken log shipper silently loses data. If the sidecar’s output matters, expose its health through a readiness probe or monitor restart counts.
Tooling that predates the field. Admission webhooks, policy engines and older operators may strip or reject restartPolicy on init containers. Policy rules that said “init containers must exit” or that validated container lists by position can also misfire. Audit your policy-as-code before rollout.
Not every helper is a sidecar. If a container is required for the Pod to function but does not need ordered start or stop, a regular container is still fine, as the documentation itself says. If it must finish before the app starts, use a regular init container. Reach for a native sidecar when you need it to be up before, and down after.
Count of probes and hooks. Sidecars can have lifecycle hooks and probes, but regular init containers cannot. When you convert a one-shot init step into a sidecar for convenience, you take on the always-on behavior and its restart semantics. Do not do this unless the process is genuinely long-lived.
The anti-pattern: sidecars as a Job scheduler
The KEP explicitly lists the abuse scenario in which a third-party orchestration tool converts all containers into sidecars so that Pods have no “main” containers. Without regular containers, nothing defines completion, so the Pod’s lifetime semantics become ambiguous. Keep at least one regular container as the unit of work whose exit defines the Pod’s outcome.
Practical Recommendations
Treat the sidecar as infrastructure with its own lifecycle contract. For each sidecar in each workload, write down three things: what the app needs from it at boot, what it needs during shutdown, and what should happen to the Pod if it fails. The answers map directly onto the startup probe, the declaration order plus grace period, and the readiness and liveness choices.
Prioritize migrations by pain. Jobs and CronJobs that hang on a helper come first, since the gain is immediate and the risk is small. Then mesh and database proxies on the request path, where startup races cause real errors. Log and metrics agents come last because they are usually tolerant.
Keep the mechanics boring. Pin sidecar image versions, give every sidecar explicit resource requests and limits, and put the sidecar’s termination budget in a comment next to terminationGracePeriodSeconds so the next engineer does not lower it.
A short checklist before you ship:
- Cluster control plane and all nodes are at v1.29 or later, and the feature metric shows enabled.
- Every webhook and operator that touches Pods preserves
restartPolicyon init containers. - Each request-path sidecar has a
startupProbe; readiness probes only where unreadiness should remove the Pod from Services. - Declaration order reflects dependencies: the thing others rely on goes first.
terminationGracePeriodSecondscovers app drain plus every sidecar’s stop time, with margin.- Alerting ignores non-zero sidecar exit codes during termination.
- At least one regular container defines the unit of work in every Job.
- A rollback manifest is stored and tested.
This post describes platform behavior as documented by the Kubernetes project and cited vendors; it is not a substitute for testing on your own clusters.
Frequently Asked Questions
What is a native sidecar container in Kubernetes?
A native sidecar is an init container with restartPolicy: Always. The kubelet starts it in order with the other init containers, keeps it running for the life of the Pod, restarts it if it exits, allows probes and lifecycle hooks on it, and stops it after the main containers have terminated. The feature is stable since Kubernetes v1.33 and was first available in v1.28.
Which Kubernetes version do I need for sidecar containers?
The SidecarContainers feature gate was alpha in v1.28, beta and on by default in v1.29, and stable in v1.33, where it is locked on. Both the API server and the nodes need to support it. The official adoption tutorial advises v1.29 or later on both, since older nodes break the feature, although v1.28 nodes with the gate enabled work if you only care about startup order.
How do I make a Job finish when it has a sidecar?
Declare the sidecar under initContainers with restartPolicy: Always instead of listing it as a regular container. The documentation states that such a sidecar does not prevent the Job from completing after the main container finishes. When the main container exits, the kubelet terminates the sidecar and the Pod completes. No wrapper scripts, shared marker files or kill endpoints are required.
In what order do sidecars start and stop?
They start in the order listed in initContainers, each after the previous container is started, which means its process is running and, if defined, its startup probe has passed. They stop in reverse order, and only after the last main container has fully terminated. The first sidecar declared is therefore the last one to receive SIGTERM.
Do sidecar containers count toward Pod resource requests?
Yes. The scheduler computes the effective request as Pod overhead plus the higher of the sum of app and sidecar container requests or the largest regular init container request. Sidecars therefore add to steady-state usage. Quota and limits are enforced against these effective values, and the Pod’s QoS class is evaluated across all init, sidecar and app containers together.
Why is my Pod stuck in Init after adding a sidecar?
The usual causes are a sidecar whose startup probe never succeeds, which blocks every later container, a cluster or node that does not have the feature enabled, or an admission webhook that stripped restartPolicy: Always so the sidecar runs as a plain init container that never exits. Check the feature metric, run kubectl describe pod to confirm the field survived admission, and read the sidecar’s logs.
Further Reading
Related posts on this site:
- Cilium service mesh without sidecars: an eBPF deep dive, for the architecture that removes the per-Pod proxy entirely.
- Kubernetes 1.37 Memory QoS and pod-level resource managers, for how pod-level budgets affect multi-container sizing.
- Ingress-NGINX retirement and Gateway API migration playbook, for traffic-path changes that interact with Pod shutdown.
Authoritative sources:
- Kubernetes documentation: Sidecar Containers
- Kubernetes documentation: Adopting Sidecar Containers
- Kubernetes blog: Introducing native sidecar containers (v1.28)
- KEP-753: Sidecar Containers
- Istio: Kubernetes Native Sidecars in Istio
By Riju — about
