5.4 Init Containers: Setup Before the Main Container Starts
Right, so you’ve got your main container all set to do its job, but it has some diva-level demands before it can start. Maybe it needs a database schema loaded, a configuration file pulled from a secure vault, or it needs to wait for some external service to become healthy. You could just jam all that startup logic into your main container’s entrypoint script, but then you’re building a Frankenstein’s monster of a container that’s hard to maintain and violates the whole “do one thing” principle.
This is where Init Containers come in. Think of them as the roadies for your rockstar main container. They set up the stage, tune the guitars, and make sure the whiskey is exactly 47.3 degrees Fahrenheit. They run to completion, in sequence, before your main container even thinks about starting. And if one fails, Kubernetes will restart the Pod (depending on your restartPolicy) until it succeeds. It’s a brutally effective way to enforce order in the chaotic startup process.
The Absolute Basics: A Simple Example
Let’s start with a dead-simple Pod manifest. Here, our main container just runs nginx, but our init container has to complete its crucial, high-stakes task first: writing a “hello world” HTML file to a shared volume.
apiVersion: v1
kind: Pod
metadata:
name: pod-with-init
spec:
volumes:
- name: shared-data
emptyDir: {}
initContainers:
- name: setup
image: busybox:1.28
command: ['sh', '-c', 'echo "<h1>Setup by the Init Container!</h1>" > /pod-data/index.html']
volumeMounts:
- name: shared-data
mountPath: /pod-data
containers:
- name: nginx
image: nginx:1.25
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
See what’s happening? Both the setup init container and the nginx container mount the same emptyDir volume. The init container writes the file, exits successfully (exit code 0), and then Kubernetes tears down its temporary runtime. Only then does it create the nginx container, which happily serves the pre-baked file. Elegant, isn’t it?
The Order of Operations is Strictly Enforced
This is the most important thing to internalize: init containers run sequentially. Kubernetes will not even pull the image for the next init container, let alone start it, until the previous one has completed successfully. This is your superpower for orchestrating complex startup routines.
Let’s say you need to wait for a database and run migrations. You’d do it in two separate init containers. Why? Because they are fundamentally different tasks with different failure modes. You want the “wait-for-db” container to retry if the database isn’t ready, but you absolutely do NOT want to run migrations on every retry. Separating them gives you that granular control.
initContainers:
# First, just make sure the DB is reachable.
- name: check-db
image: postgres:15
command: ['sh', '-c', 'until pg_isready -h my-database-host; do echo waiting for database...; sleep 2; done;']
# Then, and only then, run the one-time migrations.
- name: run-migrations
image: my-app-migrations-image:latest
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secret
key: connection-string
Resource Management: They Play by Different Rules
Here’s a fun quirk that often bites people: resource requests and limits are evaluated per container. Your init container can request a completely different amount of CPU and memory than your main app container. This is actually brilliant. Your “wait-for-db” container might just need a tiny slice of CPU, while your “compile-assets” init container might need 4 CPUs and 8Gi of memory. You can specify this right in the init container’s spec, keeping your main app’s resource profile lean and mean.
The Pitfalls: Where This All Goes Wrong
- The Infinite Loop: The most common mistake is writing an init container whose success condition is never met. Maybe it’s waiting for a service that doesn’t exist or has a bug in its completion logic. Your Pod will get stuck in
Init:0/1forever. Always test your init logic in isolation first. - Image Size: Remember, Kubernetes has to pull the image for every init container before it runs. If you have three init containers each based on a 1GB Ubuntu image, you’re in for a long wait. Use minimal base images like
busyboxoralpinewherever possible. - Security Context: If your init container is writing to a volume that your main container will read, ensure they have compatible filesystem permissions. An init container running as root writing to a volume will create files that a non-root main container might not be able to read. Set
runAsUserorfsGroupat the Pod level to avoid this.
Init containers are one of those features that feel almost too simple until you realize how many problems they solve. They enforce order, separate concerns, and make your Pod specs declarative from start to finish. Use them liberally. Your main container will thank you for its pristine, well-prepared environment. And you’ll thank yourself when you’re not debugging a 500-line entrypoint script.