Pods: The Atomic Unit
The idea in one sentence
Section titled “The idea in one sentence”A Pod is the smallest deployable unit in Kubernetes — one or more containers that always run together on the same node, sharing one network namespace and, optionally, one or more volumes.
What containers in a Pod share
Section titled “What containers in a Pod share”Every container in a Pod gets the same IP address and the same network namespace. That means two containers in one Pod reach each other over localhost, on whatever port the other one is listening on — no cluster networking involved. Containers in a Pod can also mount the same volume, which is how a sidecar reads files that the main container writes.
# From inside the "app" container, a sidecar in the same Pod is reachable on localhostkubectl exec -it app-with-sidecar -c app -- curl -s http://localhost:9090/metricsThis shared-namespace guarantee is the whole reason a Pod exists as a concept: it is the boundary within which “these containers are always co-located and can talk over localhost” holds true. Containers in different Pods never get this for free — they need a Service and a routable address.
Multi-container patterns: sidecars and init containers
Section titled “Multi-container patterns: sidecars and init containers”Two patterns cover almost every reason to put more than one container in a Pod:
- Sidecar — a helper container that runs alongside the main container for the Pod’s whole lifetime, for example a log shipper that tails the main container’s log files and forwards them, or a proxy that handles TLS termination.
- Init containers — containers that run to completion, in order, before any main container starts. A common use is waiting for a dependency to become reachable, or running a one-time migration before the main container boots.
apiVersion: v1kind: Podmetadata: name: app-with-sidecarspec: restartPolicy: Always initContainers: - name: wait-for-db image: busybox:1.36 command: ['sh', '-c', 'until nc -z db 5432; do sleep 1; done'] containers: - name: app image: myregistry/app:1.4.0 ports: - containerPort: 8080 - name: log-shipper image: myregistry/log-shipper:2.1.0 volumeMounts: - name: logs mountPath: /var/log/app volumes: - name: logs emptyDir: {}wait-for-db blocks the Pod’s startup until the database is reachable, then exits successfully; only then do app and log-shipper start, sharing the Pod’s network namespace and the logs volume.
flowchart TB
subgraph pod["Pod app-with-sidecar (one IP, one network namespace)"]
init["initContainer: wait-for-db (runs first, to completion)"] --> app["container: app"]
app -->|localhost| sidecar["container: log-shipper (sidecar)"]
end Pod lifecycle: phases, states, and restartPolicy
Section titled “Pod lifecycle: phases, states, and restartPolicy”A Pod moves through a small set of phases: Pending (accepted but not all containers are running yet, often waiting on scheduling or image pulls), Running (bound to a node, at least one container running), Succeeded or Failed (all containers have terminated), and occasionally Unknown (the Pod’s state cannot be obtained). Underneath the phase, each individual container has its own state: Waiting, Running, or Terminated.
What happens when a container exits is controlled by restartPolicy on the Pod spec: Always restarts any exited container forever (the default, used for long-running services), OnFailure restarts only on a non-zero exit code (used for Jobs), and Never never restarts (also common for Jobs, or for one-shot debugging Pods).
# Watch a Pod move through its phaseskubectl get pod app-with-sidecar --watch
# Container-level detail: state (Waiting/Running/Terminated), restart count, exit codekubectl describe pod app-with-sidecarWhy bare Pods are not how you run things
Section titled “Why bare Pods are not how you run things”A Pod is ephemeral by design and it is rarely something you create directly. If you kubectl apply a bare Pod and its node fails, or the container inside it crashes past what restartPolicy will fix, nothing recreates that Pod — it is simply gone. In practice you almost always create a Deployment, StatefulSet, or DaemonSet instead: those controllers own a Pod template, watch the actual state of the cluster, and recreate Pods whenever reality drifts from the desired count. The rest of this module is those controllers.