Init and Sidecar Containers
A Pod can hold more than its main container. Init containers run to completion, in order, before the main containers start — for setup, waiting on dependencies, or preparing a shared volume. Sidecar containers run alongside the main container for its whole life — proxies, log shippers, config reloaders.
These are the building blocks of the multi-container Pod patterns. The model got noticeably cleaner when Kubernetes introduced native sidecars, which fixed a long-standing rough edge with helper containers and Jobs.
Init Containers
Init containers run sequentially before any app container starts, and each must succeed before the next begins. They are for one-time setup: waiting until a database is reachable, running a schema migration, fetching config or secrets into a shared volume, or setting kernel parameters. Because they finish before the app starts, they are the place for "do this first" logic that would otherwise clutter the main container's startup.
spec: initContainers: - name: wait-for-db image: busybox:1.36 command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done'] containers: - name: app image: my-app:1.0
The Classic Sidecar and Its Flaw
The traditional sidecar was just an extra entry in the Pod's containers list — a proxy or log agent running beside the app. It worked for long-running Pods, but it had a lifecycle flaw: in a Job, the sidecar would keep running after the main container finished, so the Pod never reached completion. Teams worked around it with shutdown hacks. The ordering on startup was also not guaranteed relative to the main container.
Native Sidecars
Kubernetes added native sidecars: an init container with restartPolicy: Always. Declared in initContainers, it starts before the main containers (so it is ready when the app needs it), keeps running alongside them, and — critically — is terminated when the main containers finish, so Jobs complete correctly. This solved the proxy-in-a-Job problem and gave sidecars a well-defined lifecycle.
spec: initContainers: - name: proxy image: proxy:1.0 restartPolicy: Always # makes it a native sidecar containers: - name: app image: my-app:1.0
Common Patterns
The recurring shapes: a proxy/ambassador sidecar handling network concerns (the service-mesh model, Topic 24); a log or metrics sidecar reading the app's output; a config reloader watching a mounted ConfigMap and signaling the app. All share the Pod's network and can share a volume with the main container, which is exactly why they live in the same Pod rather than a separate one.
Init container — runs to completion before the app, in order. For one-time setup.
Sidecar (native) — starts before and runs alongside the app, terminated when it finishes. For continuous helpers.
App container — the main workload; the reason the Pod exists.
- Putting long-running work in an init container — it must complete, or the Pod never starts the app.
- Using a classic sidecar in a Job, so the Pod never completes after the main container exits.
- Forgetting resource requests on sidecars, which count toward the Pod's footprint and scheduling.
- Assuming startup ordering between a classic sidecar and the app — only init containers and native sidecars guarantee it.
- Reaching for a sidecar when a separate Pod would do — sidecars are for genuinely co-located helpers.
- Use init containers for one-time setup and dependency waits; keep them short and idempotent.
- Prefer native sidecars (init container with
restartPolicy: Always) so lifecycle and Job completion are correct. - Give sidecars their own resource requests and limits — they are real containers in the Pod's budget.
- Keep sidecars to genuine co-location needs (shared network or volume); otherwise use a separate Pod.
- Let a service mesh inject its proxy sidecar rather than hand-wiring one when you need mesh features.
Knowledge Check
What is the defining behavior of an init container?
- It runs to completion before the app containers start, and each init container must succeed in order
- It runs continuously alongside the app containers for the entire life of the Pod, restarting if it ever exits
- It watches and restarts each app container whenever that container crashes
- It runs only on the cluster's tainted control-plane nodes
How is a native sidecar declared, and what problem does it solve?
- As an init container with restartPolicy: Always — it runs alongside the app and is stopped when the app finishes, so Jobs complete
- As an extra entry in the containers list carrying a special sidecar annotation, which exists purely to speed up the Pod's startup time
- As a separate Pod reached over the network through its own Service
- As a DaemonSet scheduled onto the same node as the app Pod
Why did a classic sidecar break run-to-completion Jobs?
- It kept running after the main container exited, so the Pod never reached completion
- It blocked the main container from ever starting up in the first place
- It consumed all of the Pod's allotted memory and was repeatedly killed by the OOM killer
- Jobs reject any Pod template that declares more than one container
You got correct