Skip to content

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 localhost as the same interface
  • IPC — they can use shared memory and signals
  • Hostname — hostname returns 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:

  1. 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.

  2. 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.

  3. 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 migrate before the app starts
  • Secret fetching — pull credentials from Vault into a shared volume
  • Permission setup — chmod/chown on 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.