Pod
The Pod is the atomic unit of scheduling and the most important resource in Kubernetes. A Pod is a group of one or more containers that share a Linux network namespace, an IPC namespace, (optionally) a PID namespace, and a set of mountable volumes — co-located, co-scheduled, and treated by the rest of the system as a single deployable entity. The Kubernetes documentation defines it: “A Pod (as in a pod of whales or pea pod) is a group of one or more containers, with shared storage and network resources, and a specification for how to run the containers. A Pod’s contents are always co-located and co-scheduled, and run in a shared context. A Pod models an application-specific ‘logical host’: it contains one or more application containers which are relatively tightly coupled.” (kubernetes.io — Pods). Pods are deliberately ephemeral: they have a stable identity for the duration of their existence but no continuity across recreation — when a Pod is deleted, evicted, or its node fails, a controller (Deployment, StatefulSet, DaemonSet, Job) creates a new Pod with a different UID and (usually) a different name and IP, never resurrecting the dead one. This deliberate mortality is what makes the platform’s level-triggered reconciliation model work: there is no continuity to preserve across restarts, so any controller whose state lives elsewhere (in the API server’s spec, in a PersistentVolume, in an external system) can recover by simply creating fresh Pods. Every other workload resource in Kubernetes — Deployment, StatefulSet, DaemonSet, Job, CronJob — is fundamentally a mechanism for manufacturing Pods according to some rule.
Mental Model
flowchart LR subgraph POD["Pod: a single Linux 'logical host'"] PAUSE[pause container<br/>holds network &<br/>IPC namespaces] INIT[initContainer<br/>runs to completion<br/>then exits] APP1[main container<br/>e.g., nginx] APP2[main container<br/>e.g., app server] SIDE[sidecar container<br/>e.g., log forwarder] VOL[(shared volumes:<br/>configMap, emptyDir,<br/>PVC, projected, etc.)] PAUSE -.-> APP1 PAUSE -.-> APP2 PAUSE -.-> SIDE INIT -.-> APP1 VOL -.-> APP1 VOL -.-> APP2 VOL -.-> SIDE end NODE[Node<br/>kubelet + container runtime] NET[(Pod IP<br/>routable cluster-wide)] POD -- "scheduled by<br/>kube-scheduler" --> NODE NODE -- "containers share<br/>this one Pod IP" --> NET
What this diagram shows. A Pod is a bundle of co-running containers glued together by shared kernel namespaces. The first container created in the Pod is the pause container — a tiny binary whose only job is to hold open the network and IPC namespaces so that other containers can join them as they start and exit. The init containers run sequentially to completion before any main containers start. The main containers (one or more) run concurrently for the Pod’s lifetime, sharing localhost-reachable network and any explicitly-mounted volumes. The insight to extract: the Pod is not a container, and it is not a VM. It is a namespace bundle — a unit of shared isolation that lets multiple processes act as if they were on the same machine while still being delivered as separate OCI containers. This is what makes the Sidecar Pattern and Init Containers possible: they are tightly coupled to the main container in the same way two processes on a host are coupled.
Mechanical Walk-through
What a Pod literally is
A Pod is, in concrete OS terms, a set of Linux processes running inside:
- A shared network namespace. All containers in a Pod share one IP address, one routing table, one set of iptables rules, and one set of listening ports. This means containers can reach each other on
localhost:<port>and must coordinate to avoid port conflicts. The shared IP is the Pod IP, assigned by the CNI plugin when the Pod is created (see Kubernetes Networking Model). - A shared IPC namespace. Containers can use System V IPC and POSIX message queues to communicate.
- Optionally, a shared PID namespace. Controlled by
spec.shareProcessNamespace: true. When enabled, all containers see each other’s processes via/proc, which is useful for sidecar-debuggers and signal-routing patterns. Default is false. - A shared set of mountable volumes. The Pod-level
spec.volumes[]declares volume sources; each container then mounts subsets viavolumeMounts[]. This is how containers share filesystems — an emptyDir volume mounted by both lets them exchange files. - A shared cgroup hierarchy. Each container has its own cgroup leaf, but they descend from a Pod-level parent cgroup that enforces aggregate resource limits.
The Kubernetes docs make the namespace nature explicit: “The shared context of a Pod is a set of Linux namespaces, cgroups, and potentially other facets of isolation — the same things that isolate a container. Within a Pod’s context, the individual applications may have further sub-isolations applied. A Pod is similar to a set of containers with shared namespaces and shared filesystem volumes.” (kubernetes.io — Pods).
The role of the pause container
Containers come and go during a Pod’s lifetime: init containers exit, main containers may restart, ephemeral containers (kubectl debug) are injected. The kernel namespaces those containers share must outlive any individual container’s lifetime — otherwise, a container restart would destroy the namespace and force a new IP allocation. The solution is the pause container (sometimes called the “infra container”): a tiny statically-linked binary that creates the namespaces, then sleeps forever, acting as the namespace’s owner. Every other container in the Pod joins the pause container’s namespaces using setns(). The pause binary is famously small — typically 100-300 KB — and is one of the most-replicated images in any K8s cluster (one per Pod). See Pause Container for the full treatment.
Single-container vs multi-container
The Kubernetes docs are deliberately cautious about multi-container Pods:
“The one-container-per-Pod model is the most common Kubernetes use case; in this case, you can think of a Pod as a wrapper around a single container; Kubernetes manages Pods rather than managing the containers directly.”
“Grouping multiple co-located and co-managed containers in a single Pod is a relatively advanced use case. You should use this pattern only in specific instances in which your containers are tightly coupled. You don’t need to run multiple containers to provide replication (for resilience or capacity).”
The standard idioms for multi-container Pods, codified in Kubernetes Patterns by Ibryam and Huss:
- Init container (Init Containers). Sequential setup: download config, wait for a dependency, prepare a volume. Exits before main container starts.
- Sidecar (Sidecar Containers). A long-running helper co-located with the main container: log forwarder (Fluent Bit), metrics scraper, service-mesh proxy (Envoy Proxy), secrets agent (Vault Agent). The sidecar runs for the Pod’s entire lifetime. Stable as a first-class resource in K8s 1.29 (previously the same shape, just declared as a normal container).
- Ambassador. A proxy sidecar that exposes a local socket while transparently doing something else with the connection (e.g., translating from in-cluster to external, multiplexing, caching).
- Adapter. A sidecar that normalizes output from the main container before it leaves the Pod (e.g., translating app logs into Prometheus metrics, formatting application stdout into structured logs).
The common thread: the helper container is tightly coupled to the main container — same lifetime, same node, same network — and decoupling them would force the main container to take on the helper’s concerns (logging, security, transport) directly. The Pod is a way of expressing “these processes are part of one logical unit.”
Pod identity and ephemerality
The K8s docs are explicit about Pod identity: “You’ll rarely create individual Pods directly in Kubernetes — even singleton Pods. This is because Pods are designed as relatively ephemeral, disposable entities. When a Pod gets created (directly by you, or indirectly by a controller), the new Pod is scheduled to run on a Node in your cluster. The Pod remains on that node until the Pod finishes execution, the Pod object is deleted, the Pod is evicted for lack of resources, or the node fails.” (kubernetes.io — Pods).
And: “Restarting a container in a Pod should not be confused with restarting a Pod. A Pod is not a process, but an environment for running container(s). A Pod persists until it is deleted.” — this is a crucial distinction. A container’s restart policy controls whether kubelet re-runs the container inside the same Pod (preserving Pod identity, namespaces, IP, volumes); but a Pod itself, once deleted, is gone, and what replaces it is a new Pod object. The contrast manifests in how IPs work: a container restart keeps the Pod IP; a Pod recreation gets a new IP (assigned by the CNI).
Pod IP and the K8s networking contract
Every Pod gets one IP, routable cluster-wide, with no NAT between Pods. The three rules of the K8s networking model:
- Pods can reach all other Pods without NAT.
- Nodes can reach all Pods (and vice versa) without NAT.
- The IP a Pod sees itself as is the same IP others see it as.
This is enforced by the CNI plugin of choice (Calico, Cilium, Flannel, AWS VPC CNI, etc.) and is the foundation that Service abstraction sits on. The cross-link is Kubernetes Networking Model for the full treatment.
Why you almost never create Pods directly
The docs again: “Usually you don’t need to create Pods directly, even singleton Pods. Instead, create them using workload resources such as Deployment or Job. If your Pods need to track state, consider the StatefulSet resource.” (kubernetes.io — Pods).
The reason: Pods are mortal, but applications must outlive Pods. A bare Pod that crashes is gone forever; a Pod managed by a Deployment is replaced by an identical-spec sibling within seconds. The workload controllers are the platform’s answer to “how do I keep a thing running given that Pods die.” Direct Pod creation is appropriate only for:
- One-off jobs that should not be retried automatically (rare; usually a Job is better).
- Static Pods managed by kubelet without API server involvement — typically the control plane bootstrap (see Static Pods).
- Diagnostic / debug Pods (
kubectl run --rm -it ...).
For everything else, the answer is “create the parent resource and let it create Pods.”
The Pod Spec — Essential Fields
A canonical multi-container Pod with line-by-line commentary:
apiVersion: v1
kind: Pod
metadata:
name: web-app
namespace: default
spec:
# ---- Container declarations ----
containers: # one or more main containers
- name: web # container name (unique within Pod)
image: nginx:1.27 # OCI image reference
ports:
- containerPort: 8080 # informational; Pod IP exposes it directly
resources:
requests: # used for scheduling (kube-scheduler)
cpu: 100m
memory: 128Mi
limits: # used for enforcement (cgroups)
cpu: 500m
memory: 256Mi
volumeMounts:
- name: config # mount the Pod-level volume into this container
mountPath: /etc/nginx
livenessProbe: # see [[Pod Probes]]
httpGet:
path: /healthz
port: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
lifecycle:
preStop: # run before SIGTERM (see [[Pod Lifecycle]])
exec:
command: ["sh", "-c", "sleep 5"]
- name: log-forwarder # a sidecar in the classic sense
image: fluent/fluent-bit:3.1
volumeMounts:
- name: logs # shared volume between web and forwarder
mountPath: /var/log/app
# ---- Init containers ----
initContainers: # run sequentially to completion
- name: migrate-db # before any container in `containers`
image: migrate/migrate:v4.17
command: ["migrate", "up"]
# ---- Volumes ----
volumes: # declared at Pod level; mounted per-container
- name: config
configMap:
name: nginx-config
- name: logs
emptyDir: {} # shared scratch space (Pod-lifetime only)
# ---- Scheduling and identity ----
serviceAccountName: web-app-sa # Pod identity (see [[ServiceAccount]])
nodeSelector: # hard constraint on which nodes to consider
disktype: ssd
tolerations: # see [[Taints and Tolerations]]
- key: dedicated
operator: Equal
value: web-tier
effect: NoSchedule
# ---- Restart and termination ----
restartPolicy: Always # default for Pods directly; Deployments inherit
terminationGracePeriodSeconds: 30 # default; see [[Pod Lifecycle]]
# ---- Misc ----
hostNetwork: false # default; true means use Node's network namespace
shareProcessNamespace: false # default; true gives shared /proc across containers
os:
name: linux # explicit OS declaration (linux | windows)Key fields by category:
| Category | Key fields | Notes |
|---|---|---|
| Container declarations | containers[], initContainers[], ephemeralContainers[] | The actual workloads. |
| Volumes | volumes[] | Declared at Pod level; mounted per-container. |
| Networking | hostNetwork, dnsPolicy, hostname, subdomain | Override defaults; most Pods use Pod IP + cluster DNS. |
| Identity | serviceAccountName, securityContext | Pod’s API identity and runtime privileges. |
| Scheduling | nodeSelector, nodeName, affinity, tolerations, topologySpreadConstraints | Where to run. See §9 of Kubernetes MOC. |
| Restart | restartPolicy (Always/OnFailure/Never) | Default Always for Pods directly. |
| Termination | terminationGracePeriodSeconds (default 30) | See Pod Lifecycle for SIGTERM mechanics. |
| Resource limits | per-container resources.requests and resources.limits | Drives QoS class. See Resource Requests and Limits. |
Pod Lifecycle in One Sentence
A Pod transits five phases (Pending → Running → Succeeded/Failed; or Unknown if the kubelet stops reporting), with finer-grained signals delivered via conditions (PodScheduled, Initialized, ContainersReady, Ready) and per-container states (Waiting/Running/Terminated). The full treatment, including termination signals and grace periods, is in Pod Lifecycle — read it as the necessary follow-on to this note.
Failure Modes
-
Bare Pod created without a managing controller. Node fails or Pod is evicted → Pod is gone forever, never re-created. Mitigation: always use a Deployment/StatefulSet/Job/DaemonSet.
-
Container restart loop disguised as Pod failure. A container with
restartPolicy: Alwaysthat keeps crashing is restarted in-place, but the Pod stays “Running” — the symptom isCrashLoopBackOffin the container’s status, not Pod phase change. Diagnose viakubectl get pod -o yaml | grep -A5 containerStatuses. See Pod Lifecycle. -
ImagePullBackOff. Image registry unreachable or credentials missing. Symptom: Pod stuck in
Pendingwith container stateWaitingand reasonImagePullBackOff. Mitigation: verify imagePullSecrets, registry network reachability. -
OOMKilled. Container exceeded its
resources.limits.memory; cgroup OOM killer terminates it. Symptom:lastState.terminated.reason: OOMKilled. Mitigation: raise memory limits or fix the leak. -
Pod stuck Pending (no scheduling). No node satisfies nodeSelector / affinity / taints / resource requests. Diagnose:
kubectl describe podshows scheduler events. See kube-scheduler. -
Pod stuck Terminating. A finalizer is holding it (rare for Pods directly), or the kubelet is unreachable. Mitigation: see Finalizers for the finalizer case; for kubelet unreachable, consider force deletion (
kubectl delete pod --grace-period=0 --force) — but only if you understand the consequences (see Pod Lifecycle). -
Pod’s IP changed unexpectedly. Pod was recreated (different UID). The new Pod has a new IP. Any client that cached the old Pod IP is broken. Mitigation: always use Services for stable virtual IPs.
-
Multi-container port conflict. Two containers in the same Pod try to bind the same
containerPort— only one succeeds. They share the network namespace. Mitigation: coordinate ports explicitly or, better, run them in separate Pods. -
Shared-PID-namespace surprises. With
shareProcessNamespace: true, a sidecar can see and signal main-container processes. This is occasionally useful, occasionally a security concern. Mitigation: use deliberately, and only in trusted-container scenarios.
Alternatives and When to Choose Them
- VM. A Pod is not a VM — no separate kernel, no full OS. Choose a VM when you need true OS isolation (different kernel versions, untrusted multi-tenant workloads, hardware passthrough). Kata Containers (RuntimeClass) bridges the gap.
- A single multi-process container. You can run multiple processes in one container (with supervisord, dumb-init, etc.). Choose the multi-container-Pod alternative when the processes have different release cycles, security profiles, or restart semantics — which is usually.
- A separate Pod per process. Choose when processes can communicate over the network (no shared filesystem/IPC needed) and can survive independent failures.
- Native sidecars (since 1.29). For long-running helper containers with clear lifecycle ordering (terminate after the main container, restart with it), use
initContainerswithrestartPolicy: Always— the official sidecar mechanism. See Sidecar Containers.
Production Notes
- Pod templating is the workhorse. The
PodTemplateSpecembedded in Deployments, StatefulSets, DaemonSets, Jobs, ReplicaSets is the same shape asPod.spec. Learning the Pod spec is learning ~80% of what any of those resources need from you. - Pod-level resource accounting uses the sum of all containers’ requests/limits for QoS classification (QoS Classes). A Pod is Guaranteed only if every container has requests=limits for both CPU and memory.
- Pod overhead (
spec.overhead) accounts for runtime-level memory cost (e.g., the VM cost of Kata Containers). Most Pods leave this blank; it’s set automatically by the RuntimeClass when applicable. - The pause container is the most-replicated image in any cluster. Operators sometimes find pause-image misconfigurations during air-gapped install (the kubelet’s pause-image flag points to a registry the cluster can’t reach). The symptom: every Pod fails to start with ImagePullBackOff on
pause. - Pod priority and preemption (Pod Priority and Preemption) lets some Pods evict others under capacity pressure. Critical system Pods (
system-cluster-criticalpriority) survive even at cluster capacity. - k8s.af stories include outages traced to (a) bare Pods created during incident response that vanished on next node drain, (b) Pods with insufficient terminationGracePeriodSeconds that left in-flight requests dropped, (c) sidecar-init-ordering bugs where the main container started before the mesh proxy and got 503’d by everything.
See Also
- Pod Lifecycle — the state machine and termination semantics (mandatory follow-on)
- Init Containers — sequential setup containers
- Sidecar Containers — co-located long-running helpers
- Ephemeral Containers — debug-injection containers (
kubectl debug) - Pod Probes — liveness, readiness, startup probes
- Pause Container — the namespace anchor
- Static Pods — Pods managed by kubelet directly (not via API server)
- Pod Disruption Budget — declarative bound on voluntary disruption
- Pod Priority and Preemption
- Pod Overhead
- Deployment — the canonical Pod-managing resource
- StatefulSet / DaemonSet / Job / CronJob
- Kubernetes Networking Model — the Pod IP contract
- Service (Kubernetes) — stable VIPs in front of dynamic Pod sets
- kubelet — the agent that actually runs the Pod’s containers
- Container Runtime Interface — what kubelet uses to start containers
- Resource Requests and Limits — CPU/memory accounting
- QoS Classes — Guaranteed/Burstable/BestEffort
- Owner References and Garbage Collection — Pods are typically owned by ReplicaSets/StatefulSets/etc.
- Kubernetes Control Loop Pattern — Pods are inputs to every workload controller
- Kubernetes MOC — umbrella index