KubernetesKUBERNETES-SPECIFICSIMPLIFIED

Pods: The Unit That Gets Scheduled

A pod is one or more containers that share a network namespace, a lifecycle and a set of volumes — and it is the smallest thing the scheduler can place.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

Why is the unit of scheduling a pod rather than a container, and what does that grouping actually share?

The problem

Some processes genuinely must run next to each other — sharing a loopback address, a temporary directory, or a fate. Scheduling containers individually gives no way to express that, and scheduling them together gives no way to keep them independent.

What teams do first

Treat a pod as a synonym for a container, because in the overwhelming majority of manifests it holds exactly one. The extra noun looks like Kubernetes ceremony.

How it breaks

The single-container case hides what a pod is until the day you meet a second container, and then nothing about the shared network namespace makes sense.

How it breaks in production
  • The single-container case hides what a pod is until the day you meet a second container, and then nothing about the shared network namespace makes sense.
  • Pod-level fields get missed. terminationGracePeriodSeconds, the shared volume list and the pod's restart policy apply to the group, not to a container, and skipping them causes shutdown and storage surprises (Graceful Shutdown).
  • People put multiple application processes in one pod because "it is one unit anyway", coupling the lifecycle and scaling of two things that needed to scale separately.
  • Debugging misleads. kubectl logs needs a container name once there are two, and a pod can be "running" while the container you care about is in a crash loop.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • A pod is a group of containers with a shared network namespace: one IP address, one port space, and mutual reachability over localhost (Network Namespaces is the kernel primitive underneath).
  • They also share a lifecycle. The pod is scheduled once, onto one node, and it never moves. A pod is not migrated — it is deleted and a new one is created somewhere else, with a new IP.
  • They can share volumes declared at pod level and mounted into any container that wants them, which is how a sidecar reads what the main container writes (Volumes: Storage With a Lifecycle).
  • They do not share a process namespace by default, and they do not share resource limits: each container declares its own requests and limits, and each is killed independently when it exceeds memory (OOMKilled: Over the Memory Limit).
  • The pod IP is ephemeral by design. Anything that needs a durable address in front of pods needs a Service (Services: A Stable Address Over Moving Pods).
  • Init containers run to completion, in order, before app containers start — a way to express "this must be true before the process runs" that does not require the process to implement it.

What is shared and what is not

KUBERNETES-SPECIFICShared network namespace with independent filesystems and independent limits is a specific Kubernetes choice. On a VM, co-located processes share everything including the whole filesystem and a single memory budget, which is simpler and gives you no isolation between them.

The reason to know this table is that every multi-container pod question — can they talk over localhost, do they die together, can one see the other's files — is answered by it.

PropertyShared across containers in a pod?Consequence
IP address and port spaceYesReach each other on localhost; two containers cannot bind the same port
NodeYes, alwaysThe pod is scheduled once, as a unit, and never moves
Volumes declared at pod levelYes, if mountedA sidecar can read files the app writes
Lifecycle (creation and deletion)YesDeleting the pod deletes all of its containers
RestartsNoA crashing container restarts in place; the others keep running
CPU and memory limitsNoEach container is limited and OOM-killed independently (OOMKilled: Over the Memory Limit)
Filesystem rootNoEach container has its own image filesystem
Process namespaceNo, unless explicitly enabledOne container cannot see the other's processes by default

A pod spec, with the fields that are actually load-bearing

Most pod manifests are written as part of a workload controller's template rather than standalone, but the shape is the same. The fields worth arguing about are the ones below the container image.

The pod-level fields people leave out
1apiVersion: v1
2kind: Pod
3metadata:
4 name: checkout
5 labels:
6 app: checkout # how every Service and controller finds this pod
7spec:
8 terminationGracePeriodSeconds: 45 # pod-level: longer than your longest request
9 volumes:
10 - name: work
11 emptyDir: {} # shared by every container that mounts it
12 containers:
13 - name: app
14 image: registry.example.com/checkout@sha256:9f3e1c...
15 ports:
16 - containerPort: 8080
17 resources:
18 requests: # what the scheduler places against
19 cpu: 200m
20 memory: 256Mi
21 limits: # what the kernel enforces
22 memory: 512Mi
23 readinessProbe:
24 httpGet:
25 path: /ready
26 port: 8080
27 volumeMounts:
28 - name: work
29 mountPath: /var/work

The image is pinned by digest, not by a tag, so this pod is reproducible (Tags Versus Digests). The grace period is set at pod level because shutdown is a property of the group. There is no CPU limit here deliberately — CPU limits throttle rather than kill, and setting one too low is a common self-inflicted latency problem (CPU Throttling: The Latency With No Error).

When a second container earns its place

The multi-container pod is over-used by people who have just learned it and under-used by people who never did. The test is whether the two containers genuinely need to share the network namespace, a volume, or a fate — and whether they should scale together.

Should this be a second container in the pod, or a separate workload?

You have a second process that needs to run alongside your service. Where does it go?

Sidecar in the same pod

when It must share the pod's network namespace or read files the app writes — a log shipper, a proxy, a metrics adapter.

cost Scales with the app whether it needs to or not, consumes node resources per replica, and complicates shutdown ordering.

Init container in the same pod

when It must complete before the app starts — fetching a config bundle, waiting for a schema version.

cost Adds startup latency to every pod, including every restart during an incident (Startup Time & Cold Start is the cloud-side treatment).

A separate Deployment

when It has its own scaling profile, its own release cadence, or its own owner.

cost A network hop, its own Service, and its own operational surface.

Inside the application process

when It is a library concern — instrumentation, config reload — that gains nothing from process isolation.

cost Couples its failures to your process and its dependencies to your build.

How to do it properly

Most important first.

  • Default to one application container per pod. The reasons to add a second are narrow: a sidecar that must share the network namespace or a volume, or an init step that must complete first.
  • Set requests and limits on every container in the pod, since the scheduler places using requests and blind placement is a capacity failure waiting to happen (Requests and Limits).
  • Set a termination grace period that is genuinely longer than your longest in-flight request, and handle the termination signal in the process (Graceful Shutdown).
  • Never create bare pods for long-running work. Nothing recreates a bare pod when its node dies; that is what a workload controller is for (Deployments: Declaring What Should Be Running).
  • Use labels deliberately. A pod is found by every other object through its labels, so a label typo is a silent outage (Services: A Stable Address Over Moving Pods).

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongOne percent
One testEveryone
What contains it

A single misconfigured pod affects the share of traffic it was serving; the containment is the rest of the replica set continuing to serve, which is precisely what disappears if the mistake is in the template all replicas share.

What can go wrong

Failure modes, including of the mitigation
  • A bare pod on a node that dies. It is simply gone, because no controller has it as desired state.
  • Two containers in one pod that both want to be the workload, so a memory limit breach in the noisy one restarts it while the pod — and its IP — survives, producing confusing partial availability.
  • A sidecar that keeps running after the main container exits, so a pod meant to finish never does.
  • Grace period shorter than in-flight work: the container is killed mid-request during every routine rollout, showing up as a small, regular error spike nobody attributes to deploys (Deploys on the Same Timeline as the Symptom).
  • Assuming pod IP stability. Anything that caches a pod IP breaks on the next reschedule.
Misreads this invites
  • "A pod is a container." A pod is the scheduling unit; the single-container case is the common case, not the definition.
  • "Containers in a pod are isolated from each other." They share an IP and port space, so two containers cannot both bind port 8080, and either can reach the other on localhost.
  • "Pods move to another node under pressure." They are deleted and recreated. Anything held only in the pod's local filesystem is gone (Volumes: Storage With a Lifecycle).
  • "Restarting a container gives it a new IP." The IP belongs to the pod and survives a container restart within it.

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • Pod status is Running with all containers ready, and the pod appears in the endpoint list of its Service.
  • A deliberately killed pod is replaced by its controller within seconds and traffic never notices.
  • During a rollout, the error rate does not move — which is the observable form of grace periods and readiness being correct.
How you get back
  • A pod itself is not a rollback unit. You roll back the controller that owns it by restoring the previous desired state, and the pods follow (Rollback: Only Useful If It Is Actually Safe).
  • Deleting a pod owned by a controller is a restart, not a rollback: the controller immediately recreates it from the same desired state.
What to automate, and what stays human
  • Automate resource defaults and required labels through policy, so a manifest missing them is rejected at admission rather than discovered under load (Policy as Code).
  • Automate injection of standard sidecars if you use them, so their configuration does not get copied by hand into every service.
  • Keep the decision to add a second container human. It is an architecture decision about coupling, not a template choice.
What this costs
  • Grouping containers gives real expressive power — shared localhost and shared volumes — at the cost of an extra concept that most workloads never need.
  • Pods being immovable makes the model simple and makes every disruption a delete-and-recreate, which is exactly why graceful shutdown matters so much here.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • KUBERNETES-SPECIFICThe pod grouping is Kubernetes'. ECS expresses the same idea as a task with multiple containers sharing a network mode; Nomad as a task group; a PaaS usually has no equivalent at all, so a sidecar becomes a separate service with a network hop between them.
  • SIMPLIFIEDOmits pod-level security context, node affinity, tolerations, topology spread and ephemeral debug containers. All are pod-level and all are real; none is needed to understand why the unit exists.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.