
Init Containers Solve Startup Ordering That Application Code Should Not
Key takeaway: Init containers run to completion before the main container starts, which makes them the correct place for prerequisites — and the wrong place for anything that must run continuously.
The Problem They Address
A service needs its database migrated before it starts. The common approach runs migrations in the application’s entrypoint, which means every replica attempts them simultaneously on a scale-up, and the application now contains logic about deployment ordering.
An init container runs first, completes, and only then does the main container start. Migrations happen once per pod in a defined phase, and the application binary knows nothing about them.
The same pattern handles waiting for a dependency to become reachable, fetching configuration from a remote source, setting file permissions on a mounted volume, and populating a shared volume that the main container reads.
What They Guarantee
Init containers run sequentially in declared order, and each must exit successfully before the next begins. If any fails, the kubelet restarts the pod according to its restart policy.
That failure behaviour is the valuable part. A pod whose prerequisites are not met does not start serving traffic in a broken state — it stays in an initialising state where readiness checks and dashboards show it clearly.
| Use case | Init container | Sidecar | Application code |
|---|---|---|---|
| Database migration | Yes | No | Discouraged |
| Wait for dependency | Yes | No | Discouraged |
| Fetch secret at start | Yes | Sometimes | Sometimes |
| Continuous log shipping | No | Yes | No |
| Certificate renewal | No | Yes | No |
| Set volume permissions | Yes | No | No |
The distinction is duration. Anything that must keep running is a sidecar. Anything that finishes is an init container. Confusing them produces either a pod that never starts or a task that stops running after startup.
The Migration Caveat
Running migrations in an init container is better than in the application, and it still has a concurrency issue: a deployment with five replicas runs five init containers, potentially simultaneously.
Most migration tools take a database-level lock, which makes this safe but serialises pod startup. For a large migration that can delay a rollout considerably.
The cleaner arrangement is a Kubernetes Job that runs once as a deployment step, with the application init container merely waiting for the schema version it requires. That separates “apply the change” from “confirm the change is present”, and only the first needs to happen once.
Practical Details
Keep init containers small. They frequently only need a shell and a network tool, so a minimal image starts faster and reduces attack surface.
Resource requests for init containers are considered separately from the main container for scheduling — the effective request is the maximum of any single init container and the sum of main containers. An init container requesting a large amount of memory can therefore affect scheduling even though it exits quickly.
Set a reasonable timeout on waiting loops. An init container that waits forever for an unavailable dependency leaves the pod initialising indefinitely with no clear signal, which is worse than failing and restarting visibly.
The Bottom Line
Use init containers for anything that must complete before the application starts, use sidecars for anything that must run alongside it, and move migrations to a Job that runs once with the init container only verifying the result.



