Fix a Crash Loop with an Init Container and Add a Log Sidecar
Deployment `orders-api` in namespace `orders` is in CrashLoopBackOff. Its container `app` expects a configuration file that nothing creates, and the operations team also wants the application log shipped by a sidecar. The Pod already defines two `emptyDir` volumes: `config` and `logs`.
Live lab access
This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.
Create an account to continue →
On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Deployment orders-api in namespace orders is in CrashLoopBackOff. Its container app expects a configuration file that nothing creates, and the operations team also wants the application log shipped by a sidecar. The Pod already defines two emptyDir volumes: config and logs.
Requirements
1. Add an init container init-config (image busybox:1.36.1) that writes the single line mode=production to /work/config/app.conf in the config volume, then exits.
2. Add a native sidecar log-shipper (image busybox:1.36.1): it must be declared as an init container with restartPolicy: Always, mount the logs volume at /var/log/orders, and run tail -F /var/log/orders/app.log.
3. Ensure the rolled-out Pod runs app and log-shipper together, and that kubectl logs of log-shipper shows the application's order processed lines.
Keep the existing app container, volumes and mount paths unchanged.
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster. Native sidecar containers have been stable since Kubernetes v1.33, so they work on v1.35 without any feature gate.
Init containers and native sidecars: who starts first, and how they share files
A Pod's initContainers list is processed strictly in order. A normal init container must run to completion and exit successfully before the next entry starts. An entry with restartPolicy: Always is a native sidecar: the kubelet starts it, considers it done as soon as it has started, moves on, and keeps it running for the rest of the Pod's life. Only after the whole list is processed do the regular containers start. Since none of these containers share a filesystem by default, files travel through a volume, usually an emptyDir.
write-config (init) runs, writes /cfg/app.env, exits 0
v
status-reader (sidecar) started, stays up reads /out/status <--+
v | emptyDir "out"
app (main) sources /cfg/app.env, writes /out/status every 5s --+
1. Build the Pod from three roles
Save this as ticker.yaml. Two emptyDir volumes connect the containers: cfg carries one-way setup data from the init container to the app, and out carries the app's output to the sidecar.
apiVersion: apps/v1
kind: Deployment
metadata:
name: ticker
namespace: prepium-tutorial
spec:
replicas: 1
selector:
matchLabels:
app: ticker
template:
metadata:
labels:
app: ticker
spec:
volumes:
- name: cfg
emptyDir: {}
- name: out
emptyDir: {}
initContainers:
- name: write-config # 1st: runs once, must exit 0
image: busybox:1.36
command: ["sh", "-c", "echo colour=teal > /cfg/app.env"]
volumeMounts:
- name: cfg
mountPath: /cfg
- name: status-reader # 2nd: native sidecar, keeps running
image: busybox:1.36
restartPolicy: Always
command: ["sh", "-c", "while true; do cat /out/status 2>/dev/null; sleep 5; done"]
volumeMounts:
- name: out
mountPath: /out
containers:
- name: app # starts after both of the above
image: busybox:1.36
command: ["sh", "-c", ". /cfg/app.env; while true; do echo \"$(date +%T) colour=$colour\" > /out/status; sleep 5; done"]
volumeMounts:
- name: cfg
mountPath: /cfg
readOnly: true
- name: out
mountPath: /out
kubectl create namespace prepium-tutorial
kubectl apply -f ticker.yaml
kubectl -n prepium-tutorial get pods -l app=ticker -w
While setup is in progress, STATUS reads Init:N/M: N of the M init-list entries have finished. When the main container is up, the Pod reports Running.
2. Check each container's state separately
POD=$(kubectl -n prepium-tutorial get pod -l app=ticker -o jsonpath='{.items[0].metadata.name}')
kubectl -n prepium-tutorial get pod $POD \
-o jsonpath='{range .status.initContainerStatuses[*]}{.name}{"\t"}{.state}{"\n"}{end}'
kubectl -n prepium-tutorial logs $POD -c write-config
kubectl -n prepium-tutorial logs $POD -c status-reader --tail=3
write-config shows a terminated state with exit code 0. status-reader also lives under initContainerStatuses because of where it is declared, yet its state is running. Its log prints lines such as a timestamp followed by colour=teal, which proves both hand-offs: the app read the init container's file, and the sidecar read the app's file. Use -c every time; with several containers, naming the one you want avoids reading the wrong log.
3. Know the ordering guarantees
- Startup: a sidecar must report started before the next init entry runs. Without a
startupProbe, "started" just means its process is running; add a startup probe if the main app truly needs the sidecar to be serving first. - Failure: a failing init container is restarted until it succeeds (unless the Pod's
restartPolicyisNever, which fails the Pod). The status showsInit:ErrororInit:CrashLoopBackOff, and the app never starts. - Shutdown: sidecars are stopped after the main containers exit, in reverse order of declaration, which gives a log shipper the chance to read the final lines.
- Probes: ordinary init containers accept no liveness, readiness or startup probes; native sidecars accept all three.
Common mistakes
- Putting the long-running helper in
initContainerswithoutrestartPolicy: Always. It never exits, so the Pod sits atInit:1/2forever and the app never starts. - Mounting the volume under different names or paths so the writer and reader disagree. The reader reports a missing file even though both containers are healthy.
- An init command that exits non-zero, for example by writing into a directory that is not mounted. Check
kubectl logs POD -c INIT_NAMEbefore touching the main container. - Moving existing containers or volumes around while adding helpers. Change only what you need, then compare the old and new Pod specs.
| Example check | Evidence |
|---|---|
| Init container finished | Terminated with exit code 0 in initContainerStatuses |
| Sidecar declared natively | Listed under initContainers with restartPolicy: Always, state running |
| Main container healthy | Running in containerStatuses, no restarts climbing |
| Data really shared | Sidecar log shows content only the main container writes |
Cleanup: kubectl delete namespace prepium-tutorial. References: Sidecar containers, Init containers, Debug init containers. Next, try sidecars in a Job or Pod debugging.
Practice scope
This additional curriculum lab is available in single-task practice with the existing 12-minute limit. The three CKAD mocks keep their original 17-task sets. Hints, an explained solution and requirement-by-requirement feedback are available in the lab.
Browse CKAD practice tasks →