Init Containers in Kubernetes
A friendly, step-by-step guide to the small helper containers that get a pod ready before your application starts: what they are, how they run in order, how they share files, where they shine, where they hurt, and how to use them safely.
Imagine a theatre on the evening of a big show. Before the audience walks in, a crew has already been busy. Someone sweeps the stage. Someone sets the props in the right places. Someone tests the lights and the microphones. Only when every job is done does the curtain rise and the actors step out. If the crew had not finished, the actors would trip over boxes or speak into a dead microphone.
Kubernetes has exactly such a crew. Before the main application in a pod starts, one or more small helper containers run first, do their preparation jobs one after another, and then disappear. Only when every helper has finished successfully does the real application begin. These helpers are called init containers.
In this guide we will build the idea from zero, one small step at a time. Every new word is explained the first time it appears, and every big idea comes with an everyday comparison so it sticks. By the end you will know what init containers are, exactly how they behave, when to use them, when to avoid them, and how to keep them safe and healthy.
1What Are Init Containers?
To understand init containers we first need a handful of simple words: container, pod, kubelet, volume and init.
Container
A container is a small sealed box that holds a program and everything it needs to run. Kubernetes starts and stops containers for you.
Pod
A pod is the smallest unit Kubernetes runs. It holds one or more containers that live together on the same computer and share the same network address.
Kubelet
The kubelet is the agent that runs on every computer in the cluster. It starts the containers of the pods placed on its computer and watches over them.
Volume
A volume is a folder that containers in a pod can share. One container can write files into it, and another can read them.
Init
“Init” is short for “initialise”, which means “get things ready”. An init container gets the pod ready. It is a preparation step, not the main event.
App container
The ordinary containers in a pod, the ones that do the real work for your users, are called app containers to tell them apart from init containers.
So what exactly is an init container?
An init container is a special container inside a pod that runs before any of the pod’s app containers. It runs to completion, which means it does its job and then exits. If the pod has several init containers, they run strictly one after another, in the order they are written. The app containers start only after the last init container has finished successfully.
Think of it as a checklist that must be fully ticked before the doors open. Each item is a small container. If any item fails, the checklist stops, the item is tried again, and the doors stay closed until everything passes.
Think of a pilot’s pre-flight checklist. Check the fuel. Check the flaps. Check the instruments. The pilot does not skip ahead, and the plane does not take off until every item is done. The checklist items are the init containers. The flight itself is the application.
The four golden rules
Almost everything about init containers follows from four simple rules. Keep these in mind and the rest of the guide will feel natural.
They run first
Every init container must finish before any app container starts. The app never sees a half-prepared pod.
They run in order
Init containers run one at a time, top to bottom. The second does not start until the first has succeeded.
They must succeed
A failed init container is retried. The pod cannot move on while any init container is failing.
They run again on a fresh start
If the whole pod is restarted, every init container runs again from the beginning. So they must be safe to repeat.
A short history
Google released Kubernetes as open source in 2014. Very early on, users noticed that many applications need some preparation before they can start: waiting for a database, fetching a settings file, fixing folder permissions. People squeezed these tasks into complicated start-up scripts inside the main container, which made images bloated and fragile. Init containers arrived as an early experimental feature and became a stable, officially supported part of Kubernetes in version 1.6, in 2017. They have been a standard tool ever since.
An init container answers one question: “What must be true before my application starts?” Anything that is a one-time preparation step, and must finish before the app begins, is a candidate.
2The Problem Init Containers Solve
Ordinary containers all start at the same time, with no promise about order. That is fine for many programs and painful for others.
Everything starts together
In a pod, all the ordinary containers are launched at about the same moment. Kubernetes does not wait for one to become ready before starting the next. This is simple and fast, but it creates several real-world headaches.
Missing neighbours
An application needs a database or another service to be reachable at start-up. If it starts too early, it crashes, restarts, crashes again, and floods the logs with errors until the neighbour finally appears.
Missing files
The application expects a settings file, a certificate or a data set to be present when it starts. Something has to fetch or create it first.
Wrong permissions
A freshly attached storage folder is owned by the administrator, but the application runs as an ordinary user and cannot write to it. Someone has to fix the ownership before the app starts.
Heavy tools in the wrong place
Preparation often needs tools such as a git client, a download tool or a database client. Adding them to the application image makes it larger and gives attackers more to play with.
The old workaround and why it hurts
Before init containers were common, teams wrote long start-up scripts that ran inside the application container. The script would wait for the database, download files, fix permissions, and only then start the program. This works, but it has serious downsides.
The image must contain every tool the script needs, so it grows. The script runs with the same permissions as the application, so any power needed for preparation, such as changing file ownership, stays available to the application forever. Errors in the script are mixed with errors in the application, which makes logs confusing. And nobody can see from outside whether the pod is “preparing” or “running”, since it is all one container.
| Concern | Start-up script inside the app image | Init container |
|---|---|---|
| Tools needed for preparation | Bundled into the application image | Live in a separate, purpose-made image |
| Permissions during preparation | Same as the application, for its whole life | Separate, and gone once the step ends |
| Logs | Mixed with application output | Kept in a separate stream per init container |
| Visibility | Pod just says Running | Pod shows Init progress such as Init:1/3 |
| Retrying failures | Written by hand in the script | Provided by Kubernetes |
| Reuse | Copy the script into every image | Share one init image across many applications |
flowchart TB
subgraph WITHOUT["Without init containers: everything starts at once"]
direction LR
A1["Pod starts"] --> A2["App container starts immediately"]
A2 --> A3["Database or file not ready yet"]
A3 --> A4["App crashes and restarts repeatedly"]
end
subgraph WITH["With init containers: preparation comes first"]
direction LR
B1["Pod starts"] --> B2["Init container prepares and waits"]
B2 --> B3["Preparation succeeds"]
B3 --> B4["App container starts into a ready world"]
end
Consider a restaurant kitchen at opening time. The head chef could start cooking the moment the door opens and hunt for missing onions in the middle of service. Or a prep cook can arrive an hour earlier, wash, chop and stock the stations, and leave before the first customer sits down. The chef then cooks in a ready kitchen. The prep cook is the init container, and their tools, such as a big chopping board, never clutter the main stove.
Init containers are not a cure for fragile applications. A well-built application should also cope with a neighbour being briefly unavailable later on. Use init containers for real one-time preparation, not to hide poor error handling.
3Core Concepts
Init containers look like ordinary containers, but a few important differences define how they behave. Learn these and everything else will fit.
How init containers differ from ordinary containers
| Question | Init container | Ordinary app container |
|---|---|---|
| When does it start? | Before any app container | After all init containers finish |
| How many run at once? | One at a time, in written order | All together |
| How long does it live? | Until it finishes, then it is gone | For the life of the pod |
| What if it fails? | Retried; the pod cannot proceed | Restarted according to the pod’s restart rule |
| Health probes? | Not supported for the classic kind | Liveness, readiness and startup probes allowed |
| Does it receive traffic? | No | Yes, when the pod is ready |
| Can it share folders with app containers? | Yes, through volumes | Yes, through volumes |
What they share and what they keep separate
All containers in a pod share the same network address and can share volumes. Each container, however, has its own image and therefore its own file system. This is the key design idea. An init container can use a completely different image from the app, packed with exactly the tools it needs. It communicates its results to the app through a shared volume, a folder both can see.
Network
The init container and the app share the pod’s network address. An init container can reach other services just as the app would, so it can truthfully test whether they are ready.
Volumes
A folder mounted in both lets the init container leave files behind for the app. This is the main way results are handed over.
Image and tools
Each container uses its own image, so preparation tools never have to be installed in the application image.
Security settings
An init container can run with different permissions from the app. It can, for example, briefly run as an administrator to fix folder ownership, while the app runs as an ordinary user.
Resource requests
Each container asks for its own processor and memory. The pod’s overall need is worked out from all of them, as we explain in Chapter 5.
Logs
Each container has its own log stream, so preparation messages never mix with application messages.
Imagine a shared workshop with one entrance and one big table in the middle. The carpenter who prepares the wood and the painter who finishes the piece bring different tools and wear different clothes, but they share the room and the table. The carpenter finishes first and leaves the prepared wood on the table for the painter. The table is the shared volume.
Reading the pod status
Because init containers run first, the pod’s status column tells a story as they progress. You will meet these words often.
| Status shown | Meaning |
|---|---|
| Init:0/2 | The pod has two init containers, and none has finished yet |
| Init:1/2 | One of two init containers has finished; the second is running or waiting |
| Init:Error | An init container exited with a failure |
| Init:CrashLoopBackOff | An init container keeps failing, and Kubernetes is waiting longer between retries |
| PodInitializing | All init containers are done; the app containers are being created |
| Running | The app containers are up |
While init containers are running, the pod is not Ready and receives no traffic. That is exactly the point: nobody is sent to a pod that is still preparing itself.
4Architecture and Components
Init containers are not a separate machine or a separate service. They are part of the pod, managed by the same parts of Kubernetes that manage every other container. Let us meet the players.
flowchart TB
API["API Server holds the pod definition"] --> SCH["Scheduler picks a node using the pod's effective resource needs"]
SCH --> KUB["Kubelet on the chosen node"]
KUB --> SBX["Pod sandbox: network address and shared settings"]
KUB --> IC1["Init container 1"]
IC1 --> IC2["Init container 2"]
IC2 --> APP["App containers"]
VOL["Shared volume"] --- IC1
VOL --- IC2
VOL --- APP
KUB -->|"status and events"| API
Piece by piece
The pod definition is where you write the init containers. It has one list for init containers and another for the ordinary app containers. The order of the init list is the order of execution. Names must be unique across both lists, so an init container cannot share a name with an app container.
The API server stores the definition and is the notice board through which everything else learns about it. When the kubelet reports progress, such as “init container one finished”, the API server records it in the pod’s status, which is what you see with your status commands.
The scheduler decides which computer will host the pod. It must know how much processor and memory the pod needs. For a pod with init containers, this is worked out in a special way that we explain in the next chapter, so a hungry init container can affect where the pod lands.
The kubelet is the star of the show. It runs on the chosen computer, creates the pod’s shared environment, starts each init container in turn, watches for its exit, and then starts the next step. It is also the one that decides what to do when an init container fails.
The pod sandbox is the shared environment that exists before any of your containers. It holds the pod’s network address and shared settings. Because it is created first, even the very first init container already has a network address and can reach other services, which is essential for the “wait for a neighbour” pattern.
The container runtime is the software that actually creates and runs containers on request from the kubelet. It pulls images, starts processes, and reports exit codes back.
Shared volumes are the bridge between preparation and application. The most common kind is a temporary folder that is created empty when the pod starts and deleted when the pod ends. An init container fills it. The app reads it.
Think of moving into a new office. The building management (API server and scheduler) assigns you a floor. The facilities supervisor (kubelet) walks the floor. First the electricians (init containers) wire the desks, then the cleaners (a second init container) polish the floor, one after the other. The shared cupboard in the corner (a volume) is where the electricians leave the keys for the staff. Only then do the employees (app containers) walk in.
Init containers cannot be added to a running pod. The list is fixed at creation, and only the image of an init container may be changed later. Changing it restarts the pod, so it is best to change init containers through a new rollout of the workload that owns the pod.
5How Init Containers Work Internally
Here is what the kubelet does, step by step, from the moment a pod arrives on its computer.
flowchart TD
A["Kubelet picks the next init container in written order"] --> B["Pull the image if needed and start it"]
B --> C{"Did it exit with code zero?"}
C -->|"Yes"| D{"Are more init containers waiting?"}
D -->|"Yes"| A
D -->|"No"| E["Start all app containers"]
C -->|"No"| F{"Does the pod restart rule allow a retry?"}
F -->|"Yes"| G["Wait with a growing delay then restart the same init container"]
G --> B
F -->|"No"| H["Pod is marked as failed"]
Prepare the ground
The kubelet creates the pod sandbox with its network address, attaches the volumes, and pulls any secrets or settings the pod refers to. If a required secret or setting is missing, the pod cannot proceed, and its status says so.
Start the first init container
The image is downloaded if it is not already on the computer, then the container starts. The kubelet now simply waits for it to end.
Judge by the exit code
An exit code of zero means success, and anything else means failure. This is the only signal that matters. The kubelet does not read the container’s output or judge how “good” the work was.
Move on or retry
On success the kubelet starts the next init container. On failure it retries the same one, using growing waits between attempts, up to five minutes, provided the pod’s restart rule allows retrying. If the rule is “never”, the whole pod is marked failed instead.
Record the milestone
When the last init container succeeds, the pod gains a condition named Initialized. This is a public sign that preparation is complete.
Start the app containers
All app containers are started together. From here on, the usual rules of probes, readiness and restarts apply.
How the scheduler counts resources
Because init containers run one at a time, and each finishes before the next begins, they never use resources at the same moment. The app containers, on the other hand, all run together. Kubernetes therefore computes the pod’s effective need in a clever way: for each resource, it takes the larger of two numbers. The first number is the biggest request of any single init container. The second is the total of all the app containers’ requests added together.
Here is a worked example. The first init container asks for half a processor core and 256 megabytes of memory. The second asks for a fifth of a core and one gigabyte. The app containers together ask for four tenths of a core and 512 megabytes. For processor, the biggest init request is half a core, and the app total is four tenths, so the pod counts as needing half a core. For memory, the biggest init request is one gigabyte, and the app total is 512 megabytes, so the pod counts as needing one gigabyte.
The lesson is important. A heavy init container, for example one that unpacks a big archive, can make the whole pod look expensive to the scheduler for its entire life, even though that heavy work lasts only seconds. Keep init containers modest, or you may find pods refusing to fit onto computers where they would otherwise be welcome.
Think of booking a hall for a wedding. The decorators arrive first and need a large empty space to unroll their equipment. The guests arrive later and need seating for everyone. The hall you must book is large enough for the bigger of the two moments, not the sum of both, because they never overlap. But if the decorators need an enormous space, you book an enormous hall even though the guests will use a smaller part of it.
What retries really mean
When an init container fails, Kubernetes does not create a new pod. It restarts the same init container inside the same pod. Between attempts the delay grows: roughly ten seconds, then twenty, forty, and so on, up to a maximum of five minutes. This keeps a broken step from hammering the computer, but it also means a pod with a persistently failing init container may sit in the status Init:CrashLoopBackOff, retrying every few minutes for as long as nobody fixes the cause.
Notice that there is no built-in time limit for an init container. If it waits forever for a database that never appears, the pod waits forever too. The only outer limit is the pod-wide deadline, an optional setting that ends the pod after a set number of seconds no matter what. Good init containers therefore carry their own sensible time limits and give up with a clear message.
Print a clear, human-readable message from every init container: what it is waiting for, how long it has waited and why it failed. When someone looks at the logs at three in the morning, that message will be the whole investigation.
6Data Flow and Lifecycle
Now let us follow one pod through its whole life, watching what the init containers do and how their results reach the application.
sequenceDiagram
participant U as User
participant A as API Server
participant K as Kubelet
participant I1 as Init container 1
participant I2 as Init container 2
participant P as App containers
U->>A: Create the pod
A->>K: Pod is assigned to this node
K->>K: Create the sandbox and attach volumes
K->>I1: Start init container 1
I1-->>K: Exit with code zero
K->>I2: Start init container 2
I2-->>K: Exit with code zero
K->>P: Start the app containers
K->>A: Report Initialized then Ready
The stages of life
Pending
The pod exists, and is being placed on a computer and set up. The phase stays Pending throughout the whole init period.
Initialising
Init containers run one by one. The status shows how many have finished, such as Init:1/2.
Initialized
The last init container has succeeded. The pod records this milestone.
Starting
App containers are created and start. The status may briefly show PodInitializing or ContainerCreating.
Running and Ready
The app’s probes pass, and the pod begins receiving traffic. The finished init containers appear in the details as terminated with reason Completed.
Terminating
When the pod is deleted, app containers are stopped politely and the pod disappears, along with its temporary volumes.
How data moves from init container to application
The usual pattern is a shared temporary folder. The pod definition declares a volume, and mounts it into both the init container and the app container, possibly at different folder paths. The init container writes its result there, such as a downloaded settings file or a generated certificate. When it exits, the folder and its contents remain. The app then starts and simply reads the files as though they had always been there.
Other channels exist too. An init container can write to a persistent volume, which survives the pod. It can also call a remote service to register the pod or to fetch a secret. What it cannot do is pass an environment variable to the app: environment variables are fixed when each container is created, and one container cannot change another’s.
When do init containers run again?
This is the most important lifecycle question, and the source of many surprises. There are three situations.
An app container crashes and restarts
The init containers do not run again. Only the crashed app container restarts, in the same pod, with the shared volume intact. The preparation done earlier is still in place.
The whole pod is recreated
A new pod, for example after a deployment update, a scale-up, or the old pod being deleted, is a brand-new pod. Its init containers run from the very start, and the temporary volume begins empty again.
The pod’s sandbox is restarted
If the pod’s shared environment is recreated on the same computer, for instance after the computer restarts, all init containers run again, in order, before the app containers return. The same can happen if the record of a finished init container is cleaned up. This is why every init container must be safe to run more than once.
Setting up a market stall each morning is like an init container. If a customer knocks over one basket during the day, you just pick it up; you do not tear down and rebuild the whole stall. But if the stall is packed away overnight and set up again tomorrow, the entire opening routine happens again from step one.
Do not put a step in an init container that would be harmful if repeated, such as “add a row to a billing table” or “send a welcome email”. Assume every init container will run more than once, and design each so that repeating it changes nothing.
7Usage Patterns You Will Meet
Most real-world init containers fall into a small set of patterns. Learn to recognise them and you can design a good one for almost any situation.
Wait for a dependency
The most famous pattern. The init container repeatedly checks whether a database, a message broker or another service is reachable, sleeping a few seconds between tries. When the check succeeds, it exits with success and the application starts, knowing its neighbour is there. Each check is tiny, such as a network connection attempt or a name lookup.
flowchart TD
S["Init container starts"] --> P["Try to reach the database"]
P --> Q{"Did it answer?"}
Q -->|"Yes"| OK["Exit with code zero and let the app start"]
Q -->|"No"| W{"Waited longer than the limit?"}
W -->|"No"| Z["Sleep a few seconds"]
Z --> P
W -->|"Yes"| F["Print a clear message and exit with an error"]
Fetch configuration or secrets
The init container contacts a settings service or a secrets vault, downloads what the application needs, and writes it into a shared folder. The application then reads plain files and needs no knowledge of the vault, and no credentials to reach it.
Clone content from a source repository
A web server pod needs the latest site content. An init container with a git tool clones the content into a shared folder. The web server image stays small and contains no git tool at all.
Render a configuration from a template
Some programs need a settings file that combines fixed text with values known only at run time, such as the pod’s own address. An init container fills in the template and writes the finished file for the application.
Fix folder ownership and permissions
A newly attached storage volume is often owned by the administrator. An init container running with just enough power changes the ownership so that the application, running as an ordinary user, can write. The elevated power exists only during this brief step.
Tune the environment
A few programs need kernel-level settings, such as a higher limit on memory maps for search engines. An init container with special permission can apply them before the application starts. This needs care, because it requires privileges that we discuss in the security chapter.
Generate certificates or keys
The init container creates a private key and requests a certificate for this specific pod, storing both in a shared folder that only this pod can see. The application starts with its own identity ready.
Download data or models
A machine-learning service needs a large model file. An init container downloads it once at start-up, verifies it, and unpacks it into a shared folder. The service image stays small and the download logic is separate from the model-serving code.
Copy tools or plug-ins into a shared folder
Instead of baking a monitoring agent into every application image, an init container from a separate image copies the agent files into a shared folder, and the application is started with the agent attached. One agent image serves many applications.
Set up network rules
Service mesh products classically use an init container with special network permission to redirect the pod’s traffic through a helper proxy before the application starts. Users of a mesh meet this pattern without writing it.
Database schema migration, with caution
Some teams run migrations that update database tables in an init container, so that the new application version always finds the schema it expects. This can work for small systems. For larger ones it is risky, because several pods start together and each one would try to migrate. We discuss the safer alternatives in Chapter 10.
These patterns are the tasks on an airline ground crew’s list: check the runway is clear (wait for a dependency), load the passengers’ luggage (fetch data), fuel the plane (fix permissions and tune the environment), and attach the boarding stairs (copy tools). None of these are the flight itself, and all must be complete before take-off.
The more jobs you put in init containers, the slower every pod starts, and the harder it is to scale quickly under sudden load. Each init step is a delay that every single new pod pays.
8Init Containers and Native Sidecars
Recent versions of Kubernetes stretched the idea of init containers to solve a different old problem: helper containers that must start before the app and keep running beside it.
What is a sidecar?
A sidecar is a helper container that runs alongside the main application for its whole life. Common examples are a log shipper that forwards logs, a proxy that handles secure connections, and a small agent that refreshes credentials. The name comes from the sidecar of a motorcycle: an extra seat attached to the main vehicle.
For years, sidecars were just ordinary containers in the same pod, with all the problems of ordinary containers: they start at the same time as the app, with no promise that the proxy is ready before the app makes its first call, and on shutdown they may stop in any order, so a log shipper could vanish before the app finished writing its final logs. Batch jobs had a special headache: a sidecar that never exits stopped the job from ever finishing.
The native sidecar idea
Kubernetes solved this by letting an init container carry a special setting that makes it restart-always. Such an init container starts in the normal init order, but, unlike a classic init container, it does not have to finish. Kubernetes considers it “started” once it is running, and the next init container, or the app containers, can proceed. It then keeps running for the whole life of the pod, and can even have health probes.
On shutdown, the order is reversed: app containers stop first, and sidecars stop afterwards, in reverse start order. The log shipper can therefore send the last logs, and a batch job can finish, because the sidecar is shut down once the main work is done. This feature became stable in recent Kubernetes releases, so check which version your cluster runs before relying on it.
| Kind of container | When it starts | How long it lives | Probes allowed? |
|---|---|---|---|
| Classic init container | Before the app, in order | Until it finishes | No |
| Native sidecar (restart-always init container) | Before the app, in order | For the whole pod life, stopped after the app | Yes |
| Ordinary container | Together with the other app containers | For the whole pod life | Yes |
Imagine a hospital operating theatre. The set-up nurse prepares instruments and leaves (a classic init container). The anaesthetist arrives before the surgeon, stays throughout, and is the last to leave (a native sidecar). The surgeon (the app) begins only when the anaesthetist is ready, and finishes before the anaesthetist steps away.
Log shippers
They must be ready when the app starts writing logs and must outlive it to send the final lines.
Network proxies
A proxy must be running before the app makes its first outside call, or those first calls will fail.
Credential refreshers
A small agent keeps a short-lived token fresh in a shared folder for the whole life of the pod.
One-time setup
Anything that should run once and finish is still a job for a classic init container.
Ask two questions to choose. Does it need to finish before the app starts? Then it is a classic init container. Does it need to run beside the app the whole time, but start before it? Then it is a native sidecar.
9Security of Init Containers
Init containers run before your application, often with extra power, and often reach out to the network. That makes them useful, and also an attractive target.
Give the least privilege
An init container that fixes file ownership needs only the power to change ownership, not the power to do everything. Avoid the fully privileged mode whenever a smaller permission will do.
Init containers are covered by pod security rules
Cluster security policies that limit privileged or root containers apply to init containers too. An init container that demands special power may be refused in a strict namespace.
Trust the image
An init container runs before your app, with access to the pod’s shared folders and network. A poisoned image is dangerous. Use images from trusted registries, pin exact versions, and scan them.
Verify what you fetch
If the init container downloads files from outside, check a checksum or signature before trusting them. Otherwise a tampered download reaches your application quietly.
Limit who sees credentials
Give a secret only to the container that needs it. Remember that files written to a shared folder are visible to every container that mounts it.
Restrict outbound access
Where possible, allow the init container to reach only the specific places it needs. An unrestricted download step is an open door for data theft.
The shared identity
All containers in a pod share the pod’s service account, which is the identity the pod uses when it talks to the cluster itself. An init container therefore has the same cluster permissions as the app. If an init container is compromised, everything that identity may do is at risk. Keep the pod’s identity narrow.
Who can add an init container?
Anyone who can edit a workload’s pod template can add an init container, and an init container is arbitrary code that runs first, before your application’s own defences. Treat permission to edit pod templates as a powerful permission. Many organisations use admission policies to require that init containers come only from approved registries and never request privileged mode.
The night cleaning crew in an office has keys to every room. They are usually honest and essential, but that is exactly why building managers check who they hire, give them keys to only the floors they clean, and watch the entrance camera. An init container is the cleaning crew of your pod.
A short security checklist
Least privilege for each init container. No fully privileged mode unless there is no alternative. Trusted, pinned and scanned images. Verified downloads. Secrets given only where needed. Narrow outbound network access. No secrets in command arguments or logs. Approval rules for who may change pod templates.
Command arguments and environment values are visible to anyone who can read the pod definition. Never write passwords or tokens directly there. Pass them through proper secret mechanisms, and never print them in the init container’s logs.
10Choosing the Right Tool
Init containers are one of several ways to get work done around a pod. Choosing well avoids slow starts, fragile systems and surprising failures.
flowchart TD
A["I have a task to do around my app"] --> B{"Must it finish before the app starts?"}
B -->|"No"| C{"Must it run beside the app all the time?"}
C -->|"Yes"| D["Ordinary container or native sidecar"]
C -->|"No"| E["Separate job or scheduled task"]
B -->|"Yes"| F{"Is it needed for every single pod start?"}
F -->|"Yes"| G["Init container"]
F -->|"No, only once per release"| H["Separate job that runs once per release"]
Reading the decision path
Start at the top with your task. The first question is about timing: must the task finish before the application begins? If not, it does not belong in an init container. Some tasks need to run beside the app for its whole life, which points to an ordinary container or a native sidecar. Others are separate concerns that can run on their own, such as a nightly report, and should be a separate job.
If the task must finish first, ask how often. A step needed by every pod, such as fetching this pod’s certificate, is a perfect init container. A step needed only once per release, such as changing the database layout, is better as a separate job that runs once per release, before the rollout, instead of being repeated by every pod that starts.
| Tool | Runs | Best for | Weak spot |
|---|---|---|---|
| Init container | At every pod start, before the app | Per-pod preparation and readiness gates | Slows every pod start |
| Native sidecar | Before the app and beside it | Proxies, log shippers, refreshers | Needs a recent cluster version |
| Ordinary extra container | Together with the app | Simple helpers without start-order needs | No start or stop ordering |
| Separate job | Once, on demand | Migrations and one-time tasks per release | Needs its own coordination |
| Scheduled task | On a timetable | Reports, clean-ups, backups | Not tied to pod start |
| Retry logic in the app | Inside the app, always | Surviving dependencies that come and go | Requires changing the app |
| Readiness probe | Continuously | Deciding when to send traffic | Does not block the app from starting |
The case for putting resilience in the app
The wait-for-dependency init container is popular, but it has a limit. It checks the dependency only at start-up. If the database restarts tomorrow while the app is running, no init container will help. A well-built application handles missing dependencies itself, retrying connections with delays and reporting itself as not ready until they succeed. Where you can change the application, resilience inside the app is more robust. Init containers shine when you cannot change the app, or when the preparation is truly a separate concern.
Init containers versus readiness probes
People confuse these two. An init container runs first and blocks the app from starting. A readiness probe runs while the app is already running, and simply controls whether the pod receives traffic. If your worry is “do not send visitors until the app has warmed up”, that is a readiness probe. If your worry is “do not start the app until its files exist”, that is an init container.
Think of a restaurant. Setting the tables before opening is an init container. A waiter who stays on the floor all evening is a sidecar. The “open” sign that flips only when the kitchen is ready is a readiness probe. The monthly deep-clean is a scheduled job. All are useful, and each has its own moment.
Use an init container when the task is per pod, must finish first, and is a separate concern from the application code. If any of those three is missing, look at the other tools first.
11Performance and Startup Time
Every init container adds time to every pod start. That price is small for one pod and large when hundreds of pods must start quickly.
Where the time goes
Total start-up delay is the sum of several parts. First comes pulling each init image, if it is not already on the machine. Then each init container starts and runs its work, one after another. Only afterwards do the app containers begin their own image pulls and starts. Because init containers run in sequence, three steps of ten seconds each add thirty seconds before the app even begins.
Keep init images small
A small image pulls quickly. Use minimal base images that contain only the tool the step needs.
Use as few steps as possible
Merge tiny related tasks into one init container when they share an image and purpose, and split only when the tools or permissions differ.
Poll quickly but politely
A check every one or two seconds notices readiness faster than every thirty seconds, without overloading the dependency.
Cache big downloads
If an init step downloads a large file, consider a shared cache volume or baking the file into an image so repeated starts do not re-download it.
Set sensible time limits
A step that could hang forever should give up with a clear error after a reasonable time, so failures show up quickly.
Move once-per-release work out
Anything that need not run for every pod is wasting time inside an init container.
Resource reservations and scheduling
Recall from Chapter 5 that a pod’s effective request is the larger of the biggest init request and the total of the app requests. A heavy init container, for example one that unpacks a large archive, can therefore make the whole pod harder to place on a busy machine, even though that memory is needed only briefly. Give init containers realistic, modest requests, and if a step needs a large burst, ask whether it should be a separate job instead.
Scaling under sudden load
When traffic spikes and an autoscaler adds pods, each new pod must go through its init steps before it can help. If init takes a minute, the extra capacity arrives a minute late. For workloads that must scale fast, keep init lean, pre-pull images onto machines where possible, and keep slow preparation out of the start-up path.
A pizzeria that must knead fresh dough for every single order will never keep up with a rush. One that prepares dough in advance can serve the crowd quickly. Init containers that redo heavy work for every pod are the dough kneaded to order.
Measure before optimising. Look at the time between a pod being created and its app container starting. That gap is your init cost, and it often reveals one slow step that dominates all the others.
12Advantages, Disadvantages and Trade-offs
Every design choice buys something and costs something. Here is an honest ledger for init containers.
Clean separation
Set-up logic lives in its own image, apart from the application, so each can change independently.
Smaller, safer app images
Utilities and powerful tools stay out of the final application image, shrinking its attack surface.
Guaranteed order
The app never sees a half-prepared world, because Kubernetes enforces the sequence.
Temporary power
Elevated permissions exist only for the brief set-up step, not for the app’s whole life.
Built-in retries
Failures are retried automatically with growing delays, with no extra code.
Works with unmodifiable apps
Third-party images can be prepared without touching their code.
Slower starts
Each step delays every pod, including new ones created by scaling.
Start-only checks
They cannot react to dependencies that fail later while the app runs.
Harder to debug
Pods stuck in init states hide the app, and the reason lives in the init container’s own logs.
Rerun on recreation
Steps must be safe to repeat, which takes careful design.
Tight coupling in one pod
A failing init step blocks the whole pod, and many pods of the same workload together.
Limited features
Classic init containers have no probes or lifecycle hooks, which limits health reporting during long steps.
The trade-offs in plain words
The core trade-off is start-up speed against clean design. Init containers make your system tidier, safer and more predictable, and in exchange each pod takes longer to become useful. For most applications, a few seconds of start-up are a fair price. For applications that scale rapidly, the price matters and deserves measurement.
A second trade-off is start-time checks against run-time resilience. Waiting for a dependency at start-up feels reassuring, but it tempts teams to skip building real resilience into the application. The safest systems use both: an init step for genuine preparation, and application-level retries for dependencies that come and go.
A third trade-off is one bigger container against several small ones. Putting many tasks into one init container reduces overhead but mixes tools and permissions. Splitting them gives clarity and least privilege at the cost of extra image pulls and start-up steps. Choose based on whether the tasks need different tools or different powers.
A safety checklist before a flight makes aircraft far safer, at the cost of a few extra minutes at the gate. Nobody wants to skip it, but airlines still work hard to keep it short and smooth. Init containers are that checklist.
13Monitoring, Logging and Failure Handling
When a pod is stuck before its app even starts, you need to know where to look. This chapter is your troubleshooting map.
Reading the pod status
The status column of a pod listing is your first clue. It tells you which stage the pod is in, and the stage tells you where to look.
| Status you see | What it means | Where to look first |
|---|---|---|
| Init:0/2 | Zero of two init containers finished; the first is running or waiting | The first init container’s logs |
| Init:1/2 | The first finished; the second is running or waiting | The second init container’s logs |
| Init:Error | An init container exited with a failure | Its logs and exit code |
| Init:CrashLoopBackOff | An init container keeps failing and Kubernetes is waiting longer between retries | Logs of the previous attempt, plus the cause of failure |
| Init:ImagePullBackOff | The init image cannot be downloaded | Image name, tag, registry access |
| PodInitializing | All init containers finished; app containers are starting | App container events |
| Pending | The pod has not been placed on a machine yet | Scheduling events, resource requests |
A calm troubleshooting routine
First, describe the pod. The events at the bottom read like a diary of what happened: image pulls, container starts, failures and back-offs. Second, read the logs of the specific init container by naming it, since a pod’s default logs show only app containers. Third, ask for the logs of the previous attempt, because a container that crashed and restarted has fresh logs that hide the failure. Fourth, check the exit code shown in the describe output: a code of one often means the script’s own error, while codes such as 137 hint at being killed for using too much memory.
Fifth, check the shared parts. Is the volume mounted where you think it is? Does the ownership allow writing? Is the network path to the dependency open, and does the name resolve? Sixth, compare the init container’s requests and limits with what it needs, because a step killed for memory looks like a mysterious crash.
Stuck forever at Init:0/1
Usually a wait loop with no exit, a dependency that never comes, or a missing name. Check that the awaited service exists and its name is spelled right.
Init:Error, quickly repeating
The step itself fails. The logs usually say why: a missing file, a refused connection, a permission error.
ImagePullBackOff
Wrong image name or tag, or missing registry credentials. Fix the reference or the pull secret.
App starts but files are missing
The init step wrote to a folder that the app does not mount, or to a path not on the shared volume.
Pod stays Pending
Not an init problem yet. The scheduler cannot place the pod, perhaps because the effective request is too large.
Works once, fails on restart
The step was not idempotent. It assumed a clean folder and broke when files already existed.
A worked troubleshooting example
Imagine a pod that shows Init:1/2 for ten minutes. The number tells you that the first init container finished, and the second one is the suspect. You describe the pod and see, in the events, that the second init container has started once and has not restarted, so it is running rather than crashing. That already narrows the cause: it is waiting for something. You read that container’s logs and find repeated lines saying it is waiting for a service named “payments-db” to answer. You check the cluster and discover that the service is called “payments-database”. A single spelling difference kept the pod waiting. Fixing the name in the pod definition and rolling it out solves the problem.
Notice the path of reasoning. The status gave the stage, the events gave the behaviour, the logs gave the cause, and a comparison with reality gave the fix. Nothing required guessing, and the application code was never involved. With practice, this routine takes only a few minutes, and it works for nearly every init problem you will meet.
Logging good init containers
Init containers are short-lived, and their logs are valuable evidence. Write clear, human-readable lines about what the step is checking, what it found, and why it is exiting. A line such as “waiting for the database, attempt 4 of 30” saves a stranger an hour of guessing. Never print secrets. Because logs of finished pods can disappear with the pod, send logs to a central logging system so you can investigate after the fact.
Monitoring at the fleet level
Track how many pods are in init states, how long pods spend initialising, and how often init containers restart. A sudden rise in time-to-start is an early warning that a dependency has become slow. Alerts on pods stuck in initialisation for longer than a sensible limit catch quiet failures that dashboards on running apps never see, since those pods never became apps at all.
Designing for failure
Assume every init step will fail sometimes, and design so that failure is safe and visible. Make steps idempotent so retries are harmless. Set time limits so a hopeless step ends with a clear error. Emit clear messages. Keep steps small, so a failure points to one precise cause. And decide in advance whether a failure should be retried forever, as with the default policy, or should mark the pod failed, as with a never-restart policy in a batch setting.
When a flight is delayed, the departure board says exactly why: crew late, weather, or technical check. Clear status messages make delays bearable. An init container that fails silently is a departure board that just says “delayed”, leaving everyone guessing.
Always name your init containers meaningfully, such as “wait-for-database” or “fetch-certificate”. The name appears in statuses, events and log commands, and a good name shortens every investigation.
14Design Patterns and Anti-patterns
A pattern is a proven way of solving a repeated problem. An anti-pattern is a tempting approach that keeps causing pain. Knowing both saves months of learning the hard way.
Patterns worth adopting
One job per init container
Each init container does exactly one thing and is named for it. Failures point to a precise cause, and permissions can be tuned for each step.
Shared folder hand-off
The init container writes results into a shared, temporary folder. The app reads them as ordinary files. The contract between them is simply a file path.
Idempotent steps
Every step first checks whether its work is already done and skips it if so, so reruns after recreation are harmless.
Bounded waiting
Wait loops have a maximum time and print a clear message on giving up, so a missing dependency shows as a clear failure rather than an endless wait.
Tool-carrier images
A small dedicated image carries the tools the step needs, keeping the application image lean.
Least-power set-up
The powerful step runs as an init container with narrow rights, and the application runs as an ordinary user afterwards.
Anti-patterns to avoid
The giant init container
One image with every tool, doing ten unrelated jobs. It is slow, hard to secure and hard to debug.
Endless waiting
A loop that never gives up leaves pods stuck for hours with no clear error.
Migrations in every pod
Ten pods starting together each try to change the database layout at once, causing conflicts and half-finished changes.
Init as the only resilience
Relying on start-up checks alone means the app is helpless when a dependency fails later.
Privileged by default
Running init steps as fully privileged because it “just works” hands attackers a powerful foothold.
Secrets in arguments
Passwords typed straight into container arguments are visible to anyone who can read the pod definition.
Context
A team deploys an application with ten replicas. Each new release changes the database layout. Running the migration inside each pod’s init container would make ten pods attempt the same change at the same moment.
Options considered
Run migrations in every pod’s init container. Run a single separate job before the rollout. Let the application migrate itself while holding a lock.
Decision
Run one separate job per release that completes before the new pods are rolled out. Keep init containers for per-pod tasks only, such as waiting for the database to accept connections.
Consequences
Releases gain one extra coordination step, but concurrent migration conflicts disappear and each pod starts quickly. Rollback planning must consider that the database layout changes once per release, not once per pod.
Repainting a building is done once by a specialist crew before tenants return, not by every tenant painting the shared lobby each morning. Per-tenant tasks, like unlocking your own door, belong to each tenant. Init containers are for the unlocking, not the repainting.
15Best Practices and Common Mistakes
This chapter gathers everything into checklists you can pin beside your desk.
Best practices
Design
Give each init container one clear job and a meaningful name. Make each step idempotent. Bound every wait with a time limit. Use a shared temporary volume to hand over results. Keep once-per-release work in separate jobs. Also build retry logic into the application itself.
Images
Use small images from trusted registries. Pin exact versions rather than a floating “latest” label so that every pod starts with identical tools. Scan images for known weaknesses and rebuild them regularly.
Security
Grant only the permissions each step needs. Avoid fully privileged mode. Keep secrets out of arguments and logs. Verify downloads. Restrict the pod’s service account to what it truly needs, and restrict who can edit pod templates.
Resources
Set realistic requests and limits on init containers. Remember that the largest init request can dominate the pod’s scheduling needs. Watch for steps killed for exceeding memory.
Operations
Write clear log messages. Ship logs to a central system. Alert on pods stuck initialising. Track time-to-start. Document what each init container does, why it exists and what it expects from its neighbours.
Common mistakes
| Mistake | Why it hurts | Better approach |
|---|---|---|
| Forgetting init containers rerun | Non-repeatable steps break after a pod is recreated | Make each step safe to run again |
| Writing to a folder the app does not mount | App starts and finds nothing | Mount the same shared volume in both places |
| Wait loops with no limit | Pods hang without a visible error | Add a time limit and a clear failure message |
| Using floating image labels | Different pods get different tools | Pin exact versions |
| Expecting probes on classic init containers | Not supported, so long steps look silent | Log progress, or use a native sidecar where suitable |
| Changing init settings and expecting a live update | Only the image field can change on a running pod | Roll out a new pod definition |
| Reading the wrong logs | Default log view skips init containers | Name the init container when reading logs |
| Giving a huge memory request | Pod becomes hard to schedule | Use realistic requests or move heavy work to a job |
| Blaming the app when the pod is stuck | The app has not even started | Check the init state first |
Whenever you see an init container in a review, ask three things: is this per pod, can it safely rerun, and does it have the least power it needs? Those three questions catch most problems.
16Real-World Usage Stories
These generic stories, based on common patterns rather than any specific company, show how init containers appear in everyday systems.
The online shop that started before its database
A small online shop’s web pods kept crashing after every cluster maintenance window. The cause was simple: the database took longer to come back than the web pods. The team added a small init container that waited, with a limit, for the database to accept connections. Crash loops during maintenance vanished, and the application code stayed untouched. Later they also added connection retries inside the application to survive brief database restarts during normal running.
The media site with fresh content
A publishing team served static pages from a lightweight web server. Content changed often, and rebuilding the server image for every article was slow. They used an init container that fetched the latest published content into a shared folder before the web server started. The web server image changed rarely, and content updates required just a pod restart.
The bank-style service with strict secrets
A service needed credentials held in a central vault. The team refused to give the application any vault knowledge. An init container authenticated to the vault, fetched only that service’s secrets, and wrote them to a memory-backed shared folder. The app read them as plain files. The vault client library never entered the application image, and the audit trail was simple.
The analytics platform with permission trouble
A data team attached persistent storage to a processing pod and found the app could not write, because the volume belonged to the administrator. Running the whole app as administrator was rejected by the security team. An init container with just the ownership-changing power fixed the folder, then exited. The app ran as an ordinary user, and the security team approved.
The machine-learning service with a huge model
Model files were several gigabytes, too big to bake into every image version. An init container downloaded the model from storage, verified its checksum and placed it in a shared folder. Start-up was slower, so the team later added a cache volume, and the delay dropped sharply on repeat starts. They also learned to alert on pods stuck initialising, since downloads occasionally stalled.
The service mesh that hides its init step
Teams using a service mesh often never write the network-rules init container that the mesh injects automatically into their pods. They meet it only when a pod shows a mysterious extra init step, or when a strict security policy refuses it because it needs network administration permission. Understanding init containers turns that mystery into a five-minute fix.
These stories are like the small print of a building’s daily opening routine: unlock doors, switch on lights, check the alarm, stock the till. Customers never notice the routine, and only notice when it is skipped. Init containers are your system’s opening routine.
Three worked scenarios: thinking like a designer
Stories are useful, but the real skill is deciding what to do with a fresh problem. Here are three short scenarios, each with the reasoning a careful designer would follow.
Scenario one: the application needs a generated settings file
An application reads a settings file at start-up, and the file must contain this pod’s own address and a value from a central settings service. Ask the three questions. Is it per pod? Yes, the address differs for every pod. Must it finish before the app starts? Yes, because the app reads the file immediately. Is it a separate concern from the application code? Yes, since the application should not know about the settings service. All three answers point to an init container. The design is simple: one init container, named for its purpose, mounts a shared temporary folder, fetches the value, fills in the template, and exits. The application mounts the same folder and reads the finished file. To make it safe on rerun, the init container overwrites the file completely instead of appending to it.
Scenario two: the report generator that needs fresh data every night
A team wants a nightly job that refreshes data before a reporting service serves it. The first idea is an init container that refreshes data whenever a pod starts. Test it against the questions. Is it per pod? No, the refresh happens once a night for the whole system. Would ten pods each refresh the same data? Yes, wastefully and possibly in conflict. The answer is a scheduled task that runs once per night, with the reporting pods simply reading the result. The lesson is that an init container is the wrong tool whenever the work is about the system as a whole rather than about one pod.
Scenario three: a secure proxy that must outlive the application
An application must send all its outside traffic through a secure proxy, and the proxy must be running before the first request and must keep running until the last response. A classic init container cannot do this, because it must finish before the app starts, which would stop the proxy. A native sidecar fits well, since it starts before the app, stays alongside it, and stops after it. If the cluster version does not support native sidecars, the team falls back to an ordinary extra container, and adds retry logic to the app for the first few seconds, accepting the weaker ordering.
A short mental checklist for new designs
Before adding any init container, pause and walk through a short list. State the job in one sentence. If you cannot, the container is doing too much. Decide what it needs to read and what it must write, and confirm the shared folder both sides will use. Decide what happens when it runs twice. Decide what happens when it runs for an hour, and set a limit. Decide which permissions it needs, and remove everything else. Decide what a stranger would see in its logs at three in the morning. Finally, estimate the time it adds to every pod start and ask whether that price is acceptable. Ten minutes on this checklist prevents most of the problems described in this article.
17Frequently Asked Questions
Quick answers to the questions beginners ask most often.
This post was written without live web research. Please verify version-sensitive details, such as native sidecar support, against the official documentation for your cluster version.
18Summary and Key Takeaways
Let us close the loop, returning to our theatre crew from the start of this article.
Just as a stage crew sets the scene before the curtain rises and only then lets the actors begin, init containers prepare a pod before its application starts. They run in order, each one finishing before the next begins. They can carry different tools, hold different permissions and produce results in shared folders. They retry when they fail, and they rerun when a pod is recreated, which is why they must be safe to repeat.
You met the problem they solve, the ordering headache of starting an app before its world is ready. You saw their core concepts, their architecture, and how the kubelet drives them through a decision loop. You followed data through the lifecycle, met the usage patterns, compared them with native sidecars, and learned how to secure, size, monitor and troubleshoot them. You also saw where they do not belong.
Before, in order, to completion
Init containers run before app containers, one at a time, each to success.
Shared volumes are the hand-off
Files passed through shared volumes are how init steps deliver their results.
Design for reruns
Idempotent steps make recreation and retries safe.
Least privilege
Give each step only the power it needs, and keep secrets out of arguments and logs.
Mind the start-up cost
Every step delays every pod, so keep them few, small and bounded.
Use the right tool
Per-pod, must-finish-first, separate-concern tasks fit init containers. Others fit jobs, sidecars or the app itself.
Version drift and things to verify
Kubernetes changes steadily, and details around init containers have shifted over the years. Native sidecar support arrived much later than classic init containers, and the exact behaviour of features such as probes on sidecars, shutdown ordering and resource accounting has been refined across releases. Because this article was written without live web research, treat version-specific statements as a starting point. Before relying on a feature in production, read the official documentation for your cluster’s exact version, try the behaviour in a test namespace, and note the version in your team’s own documentation so that future upgrades prompt a re-check.
It also helps to know your managed platform. Cloud providers sometimes run older or newer versions than you expect, and some add their own admission rules that change what init containers may do, such as blocking privileged mode or requiring images from an approved registry. A five-minute check of your platform’s rules can save an afternoon of confusing errors.
A small glossary to keep nearby
Pod
The smallest deployable unit in Kubernetes: one or more containers sharing a network address and storage.
Init container
A helper container that runs to completion, in order, before the application containers start.
Native sidecar
An init container set to restart always, so it starts early and keeps running beside the application.
Idempotent
Safe to run more than once, because repeated runs leave the same final result.
Kubelet
The agent on each machine that starts and watches the containers of pods placed there.
Shared volume
A storage area mounted by several containers in the same pod, used to hand over files.
Back-off
A growing pause between retries, so a failing step does not hammer the system.
Effective request
The resource amount the scheduler reserves for a pod, considering both init and application containers.
Look at one real pod in your own cluster. Find any init containers, read their names, and ask whether each is per pod, repeatable and least-privileged. That small review teaches more than any article.