The Hidden Container Running in Every Kubernetes Pod¶
Run docker ps on any Kubernetes node and you will see containers you did not create. For every pod, there is a pause container sitting quietly alongside your application. It consumes almost no resources, it never logs anything, and most engineers never notice it.
But it is the reason your pod works at all.
What the pause container does¶
The pause container serves exactly one purpose: it holds the Linux namespaces that define the pod.
When Kubernetes creates a pod, it does not start your application container first. It starts the pause container, which creates the network namespace, the IPC namespace, and the PID namespace. Your application container then joins those namespaces.
graph TD
Pod["Pod"]
Pause["pause container<br/><i>creates & holds namespaces</i>"]
App["app container<br/><i>joins namespaces</i>"]
Sidecar["sidecar container<br/><i>joins namespaces</i>"]
Pod --> Pause
Pod --> App
Pod --> Sidecar
Pause -. "network ns" .-> App
Pause -. "network ns" .-> Sidecar
Pause -. "IPC ns" .-> App
Pause -. "IPC ns" .-> Sidecar
This is why containers in the same pod share:
- Network — they see
localhostas the same interface - IPC — they can use shared memory and signals
- Hostname —
hostnamereturns the same value
Why not just use the app container?¶
You might wonder: why not let the first application container create the namespaces?
Three reasons:
-
Lifecycle independence — if your app crashes and restarts, the namespaces survive because the pause container is still running. Other containers in the pod keep their network connections.
-
PID 1 responsibilities — in Linux, PID 1 has special duties like reaping zombie processes. The pause container handles this so your app does not have to.
-
Simplicity — the pause container is a ~700KB static binary that calls
pause(). It does one thing and cannot fail in interesting ways.
Seeing it in action¶
On a node with crictl access:
# List all containers in a pod
crictl ps --pod <pod-id>
# You will see something like:
# CONTAINER ID IMAGE STATE NAME
# abc123 registry.k8s.io/pause Running POD
# def456 my-app:latest Running my-app
The pause container's image is registry.k8s.io/pause:3.9 (or similar). It is pulled once and cached — you will never wait for it.
Init containers: the other hidden containers¶
While we are talking about invisible infrastructure, init containers deserve a mention. These run before your application starts and must complete successfully:
apiVersion: v1
kind: Pod
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nslookup postgres; do sleep 2; done']
containers:
- name: app
image: my-app:latest
Common uses:
- Waiting for dependencies — database, service mesh proxy, config server
- Schema migrations — run
flyway migratebefore the app starts - Secret fetching — pull credentials from Vault into a shared volume
- Permission setup —
chmod/chownon mounted volumes
Debugging stuck pods
Init containers run sequentially. If any fails, the pod stays in Init:CrashLoopBackOff and your application never starts. Always check init container logs first.
Here is a quick debugging checklist:
- Check pod status with
kubectl get pod <pod> - Describe the pod to see init container state:
kubectl describe pod <pod> - Read init container logs:
kubectl logs <pod> -c <init-container-name> - Verify the dependency is actually reachable from the pod's network namespace
- Check if the init container image can be pulled (registry credentials, network policy)
The takeaway¶
Kubernetes is not magic — it is Linux namespaces, cgroups, and a lot of coordination. The pause container is the simplest piece of that machinery, and understanding it makes the rest of the system less mysterious.
Next time you see a pod stuck in CrashLoopBackOff, remember there are more containers involved than the ones in your YAML.