The question this answers
What is the unit Kubernetes actually schedules, and when does more than one container legitimately belong inside it?
The API container needs a log shipper that reads the same volume and dies when the API dies. It also needs one address that both processes share, so a request to port 8080 and a metrics scrape on 9090 reach the same replica.
A scheduling and lifecycle boundary: everything inside a pod is placed on one node, shares one IP address and one localhost, and is created and destroyed as a single unit.
What the boundary actually contains
A pod is a group of containers that share three things: a network namespace, so they have one IP and can reach each other over localhost; optionally volumes, so they can see the same files; and a lifecycle, so they are scheduled together onto one node and torn down together. That is the entire concept. Everything else you know about pods is a consequence of those three.
The network property is the one that surprises people coming from Docker. Two containers in the same pod cannot both bind port 8080 — they are in the same network namespace, so it is the same port. Two containers in *different* pods can both bind 8080 quite happily, because each pod has its own namespace and its own address. The Operating Systems domain teaches namespaces properly; what matters here is the operational consequence: the pod, not the container, is the thing with an address.
The lifecycle property is why "shared lifecycle" is the right test for whether something belongs in the pod. If process B is useless without process A on the same machine, and should disappear the moment A disappears, it is a sidecar. If B is independently useful, independently scalable or independently releasable, it is a separate workload and putting it in the pod is a mistake you will pay for on the first scaling decision.
The sidecar test, and the three patterns that pass it
Only a small number of patterns genuinely need a second container. A log or metrics shipper that reads a shared volume and must not outlive the workload. A network proxy that intercepts the pod's traffic — a service mesh dataplane is the canonical case, and it is the one that most often is not worth its cost at small scale. An init container that runs to completion before the main container starts: a schema migration, a config fetch, a permissions fix on a mounted volume.
Everything else fails the test. The pattern below is the single most common misuse: an application and its database in one pod, because "they belong together". They do not. They have different resource shapes, different failure semantics, different backup requirements and, above all, different scaling: scaling the API to five replicas would give you five databases with five separate disks and no replication between them.
The cost of a needless sidecar is not theoretical. Every container in the pod is scheduled on the same node, so its requests add to the pod's total and make placement harder. Every container extends startup, which lengthens every rollout and every autoscaling response — see Startup Time & Cold Start. And a sidecar that crashes can take the pod's readiness with it, so the helper you added for observability becomes the reason the workload is out of rotation.
spec:
containers:
- name: api
image: registry.example/api:v7
- name: postgres # <-- scales with the API, dies with the API
image: postgres:16
volumeMounts:
- { name: data, mountPath: /var/lib/postgresql/data }
# replicas: 3 -> three unrelated databases, three disks, no replication,
# and a rolling update that destroys a database per step.spec:
initContainers:
- name: fix-perms # runs to completion before the app starts
image: busybox:1.36
command: ['sh', '-c', 'chown -R 1000:1000 /var/log/app']
containers:
- name: api
image: registry.example/api:v7
volumeMounts: [{ name: logs, mountPath: /var/log/app }]
- name: log-shipper # useless without the api, dies with it
image: registry.example/fluent:v2
volumeMounts: [{ name: logs, mountPath: /var/log/app, readOnly: true }]
# The database is a managed service reached over the network.
# See managed-databases and stateful-workloads for why.The test is fate sharing, not conceptual relatedness. The log shipper is meaningless without this specific replica; the database is meaningful without any of them, and needs to survive all of them.
The lifecycle you actually have to design around
Pods are ephemeral by contract. They are created, they get an IP nobody chose, they are deleted, and their replacement is a different pod with a different IP and no memory of the first. Every design decision in the rest of this module follows from that: Service: A Stable Name in Front of Moving Pods exists because the IP changes, Stateful Workloads: Databases Are Not Stateless APIs exists because most pods must not keep data, and Graceful Shutdown: The 502 Spike Nobody Investigates exists because deletion is not instantaneous and in-flight requests are real.
The termination sequence is where teams lose requests. When a pod is deleted, two things happen *concurrently*: it is removed from service endpoints, and it receives a termination signal. Neither is instant, and the ordering is not guaranteed across the whole cluster. An application that exits immediately on the signal will drop requests that were routed to it microseconds before the endpoint update propagated. The fix is in the application: catch the signal, stop reporting ready, finish in-flight work, then exit.
- 1Pendingms to seconds
The pod object exists; no node has been assigned yet.
Stays here forever if no node satisfies its requests or constraints — see Scheduling: How a Pod Chooses a Node.
- 2ContainerCreatingseconds to minutes
Node assigned; the agent pulls images, attaches volumes and sets up the network namespace.
Image pull failures, registry credentials, a volume that will not attach. This is where a large image costs you real time.
- 3Initseconds
Init containers run to completion, in order.
A failing init container blocks the pod indefinitely and the main container never starts.
- 4Running, not Readyseconds to a minute
Containers started; the readiness probe has not yet passed. No traffic is routed here.
A readiness probe with too short an initial delay marks a healthy slow starter as broken.
- 5Ready
Readiness passes; the pod is added to service endpoints and receives traffic.
A readiness probe that only checks the process is alive, not that dependencies are reachable, admits traffic too early.
- 6Terminatinggrace period, commonly 30s
Endpoint removal and the termination signal happen concurrently; the grace period starts.
Exiting immediately on the signal drops in-flight and just-routed requests.
- 7Gone
The container is force-killed if still alive, and the pod object is removed.
Work that was not finished or checkpointed is lost. The replacement has a different IP and no state.
Key points
- A pod shares three things among its containers: a network namespace (one IP, one localhost, one port space), optional volumes, and a lifecycle.
- The test for a second container is fate sharing — is it useless without this exact replica, and must it die with it? — not conceptual relatedness.
- Most pods should contain exactly one container. Log shippers, traffic proxies and init containers are the legitimate exceptions.
- An application and its database in one pod scales into N unrelated databases with no replication; it is the classic misuse.
- Termination removes the pod from endpoints and signals it concurrently, so dropping in-flight requests is the application's bug to fix, not the cluster's.
The loop, answered
Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.
- • The scheduler places the whole pod on one node; the sum of all its containers' requests is what must fit.
- • The node agent creates the pod's network namespace first, then starts init containers in order, then the main containers.
- • All containers in the pod share that namespace, so they reach each other over
localhostand cannot bind the same port. - • Volumes declared on the pod are mounted into whichever containers ask for them, which is how sidecars read the workload's files.
- • On deletion, endpoints are updated and a termination signal is sent; after the grace period anything still running is killed.
- • Signal handling in the application — this is application code, and no cluster setting substitutes for it.
- • Probe tuning per container, including an initial delay honest about how long your process really takes to become useful.
- • Resource requests for every container in the pod, sidecars included; a sidecar with no request is capacity you did not account for.
- • Image size for every container in the pod, since each one is pulled before anything starts — see Why Image Size Is an Infrastructure Problem.
- • A sidecar crash-looping takes the pod out of rotation even though the application container is perfectly healthy.
- • Two containers configured on the same port: the second fails to bind, with an error that reads like an application bug.
- • A failing init container: the pod sits in
Init:CrashLoopBackOffforever and the main container never runs. - • Immediate exit on the termination signal: a burst of 502s during every deploy, proportional to traffic and invisible in low-traffic testing.
- • An
emptyDirvolume treated as durable: it is deleted with the pod, and the data was never anywhere else.
- • Pods are the unit of horizontal scaling, so anything inside a pod scales at the pod's rate whether that suits it or not.
- • Every additional container multiplies its own resource requests by the replica count — a 100 MiB sidecar across 200 replicas is 20 GiB of cluster memory.
- • Startup time is the sum of pulling and starting every container, and it sets the floor for how fast autoscaling can respond.
- • Containers in a pod share a network namespace, so a compromised sidecar can reach the application over localhost, bypassing any network policy.
- • They can also share volumes, so a sidecar with a writable mount has the same file access as the workload.
- • Run containers as non-root with a read-only root filesystem and no added capabilities unless something concretely requires otherwise; the isolation guarantees themselves are an operating-system concern.
- • The pod is the identity boundary for workload credentials: every container in it can reach the same service-account token unless you explicitly prevent it.
- • Cost is driven by the sum of requests across all containers in the pod, multiplied by replicas — sidecars are a real and frequently unbudgeted line.
- • A mesh dataplane sidecar on every pod is one of the largest hidden costs in a Kubernetes platform, in memory and in latency.
- • Larger pods bin-pack worse: a pod requesting most of a node wastes the remainder, which shows up as low utilization at constant spend.
- • Per-container restart counts within the pod, so a crash-looping sidecar is distinguishable from a crash-looping application.
- • Time from pod creation to Ready — the number that governs both rollout speed and autoscaling responsiveness.
- • Termination-related errors at the load balancer during deploys, which is where graceful-shutdown bugs actually show up.
- • The signal that lies: pod phase
Running. It means containers started, not that the readiness probe passed or that traffic is being served.
- • One container per pod, which is the correct answer for most workloads and should be the starting assumption.
- • A node-level agent (one per machine) instead of a per-pod log shipper — one copy per node rather than one per replica, at the cost of less isolation.
- • A library inside the application instead of a proxy sidecar, when the feature is retries or timeouts rather than transparent traffic interception.
- • On a single VM, a supervisor with two processes achieves the same fate sharing with none of this machinery.
- • Buys a clean fate-sharing boundary and localhost communication; costs the temptation to co-locate things that should scale separately.
- • Buys transparent cross-cutting features via sidecars; costs memory, startup time and an extra failure source on every single replica.
- • Buys an ephemeral, replaceable unit that makes healing easy; costs the requirement that your application tolerate being killed at any moment.
What people believe, and what is true
A pod is just a wrapper around a container.
It is a shared network namespace, an optional set of shared volumes and a shared lifecycle. Those sharing properties are the entire reason it exists.
Containers in a pod are isolated from each other.
They share the network namespace and can share volumes. A compromised sidecar reaches the application over localhost with no network policy in the way.
Multi-container pods are an advanced best practice.
They are a narrow tool for fate-sharing helpers. Most well-run production pods contain exactly one application container.