arrow_backBACK TO KUBERNETES FOR BACKEND ENGINEERS — PODS, SCALING, NETWORKING AND PRODUCTION
Lesson 03Kubernetes for Backend Engineers — Pods, Scaling, Networking and Production5 min read

Pods: the Smallest Unit

October 27, 2026

TL;DR

A pod is the smallest thing Kubernetes runs: one or more containers that share a network namespace (one IP, reachable on localhost) and can share storage volumes, scheduled together on one node, started and stopped together. Usually a pod is one main container; a second container is a sidecar (a log shipper, proxy, or config reloader) that supports the main one. Keep pods small; don't cram unrelated services together. You almost never create pods directly, a bare Pod has no self-healing above the node. Instead you create a controller that manages pods for you: Deployment for stateless apps, StatefulSet for things with identity and storage (databases), DaemonSet for one pod per node (agents), Job for run-once tasks, CronJob for scheduled tasks. Rule: stateless to Deployment, identity to StatefulSet, per-node to DaemonSet, finite work to Job/CronJob.

Beginners often expect the basic unit of Kubernetes to be the container. It isn’t, it’s the pod. This trips people up until they see why, and then a lot of Kubernetes behaviour suddenly makes sense: why two containers can share localhost, why they always land on the same machine, why scaling means “more pods” not “more containers”. Let’s build the right mental model of a pod, and then see why you’ll rarely create one by hand.

What a Pod Is

A pod is one or more containers that are treated as a single unit. The containers in a pod:

The pod: one or more containers that share a fate. A pod with one IP and one lifecycle holds an app container (your service, listens on :8080) and a sidecar (log shipper or proxy, sees localhost:8080), plus shared network namespace and optional volumes so both reach each other on localhost with one pod IP for the whole pod. Scheduled together: all containers in a pod land on the same node, start and stop together, scale together. So keep pods small: one main container plus maybe a sidecar, don't cram unrelated services into one pod. Why not just run containers? A bare container that crashes stays dead; a pod is watched and restarted, though you rarely create pods directly (you create a Deployment). Sidecars like a service mesh proxy, log collector or config reloader are the main reason a pod holds more than one container
  • Share one network address. The pod gets a single IP. Containers in it reach each other on localhost and share ports (so two containers in a pod can’t both use port 8080). To the rest of the cluster, the whole pod is one network endpoint.
  • Can share storage. Volumes defined on the pod can be mounted into multiple containers, so they can share files.
  • Share a lifecycle. They’re scheduled onto the same node, started together, and stopped together. They live and die as a unit.

Because they share a fate, containers in a pod should be tightly coupled, things that genuinely belong together on the same machine. Most pods have exactly one container: your service. The pod is just the wrapper Kubernetes schedules and heals.

When a Pod Has More Than One Container: Sidecars

The reason to put a second container in a pod is the sidecar pattern: a helper container that supports the main one and benefits from sharing its network and storage. Common sidecars:

  • A service-mesh proxy (Istio’s Envoy, Linkerd) that intercepts the pod’s traffic for mTLS, retries and metrics.
  • A log shipper that reads the app’s log files (shared volume) and sends them off.
  • A config reloader that watches for config changes and signals the app.
  • An init container (a special case): runs to completion before the main containers start, for setup like running database migrations or waiting for a dependency.

Kubernetes even has first-class sidecar containers (a special kind of init container that keeps running) so the mesh proxy starts before and stops after your app. But the rule stands: a sidecar supports the main container. Don’t put two unrelated services (your API and your separate billing service) in one pod just to save pods, they’d be forced onto the same node, scaled together, and killed together, which is almost never what you want. If two things scale or fail independently, they belong in separate pods.

Why You Rarely Create Pods Directly

You can create a bare Pod, but you almost never should. A lone Pod has a critical weakness: no self-healing above the node level. The kubelet will restart a crashed container within the pod, but if the whole pod is deleted, or its node dies, nothing recreates it. There’s no controller watching to keep it alive. It also has no rolling updates, no scaling, no rollout history.

Instead, you create a controller that manages pods for you, the control loop from Lesson 1. You declare “I want 3 of these pods”, and the controller creates them, replaces any that die, and reschedules them if a node fails. You describe the pods you want via a template inside the controller, and the controller does the rest.

So in practice, “creating pods” means creating a Deployment (or similar), which creates the pods. Bare pods are for quick debugging only (kubectl run -it debug --image=busybox --rm).

Which Workload Controller?

Different work behaves differently, so Kubernetes has several controllers. Picking the right one is mostly about how the workload behaves:

Which workload object do you use? You almost never create Pods directly. Deployment: for stateless apps like APIs, web and workers, with interchangeable pods, rolling updates, easy scale and rollback. StatefulSet: for databases, queues, anything with identity, giving stable names like db-0 and db-1, stable storage and ordered start. DaemonSet: one pod per node, for log agents, monitoring and CNI, following nodes as they come and go. Job: run once to completion, for batch tasks and migrations, retrying until it succeeds. CronJob: run on a schedule, for nightly reports and cleanups, creating a Job each time. Pod bare: almost never, for debugging only, with no self-healing and no rollout. Rule: stateless to Deployment, identity or stable storage to StatefulSet, per-node to DaemonSet, finite work to Job or CronJob
  • Deployment (Lesson 4): for stateless apps, APIs, web servers, workers. Pods are interchangeable: any pod can serve any request, and a dead one is replaced by a fresh blank one. Gives rolling updates, easy scaling and rollback. This is what you’ll use most.
  • StatefulSet (Lesson 11): for workloads with identity and state, databases, Kafka, ZooKeeper. Each pod gets a stable name (db-0, db-1), keeps its own storage, and starts/stops in order. A replacement db-1 reattaches db-1’s disk.
  • DaemonSet: for one pod per node, log collectors, monitoring agents, the network plugin. As nodes are added or removed, the DaemonSet adds or removes a pod to match.
  • Job: for run-once work that must complete, a data migration, a batch computation. It retries until it succeeds, then stops.
  • CronJob: for scheduled work, a nightly report, a cleanup task. It creates a Job on a schedule (like cron).

The quick rule: stateless → Deployment; needs stable identity or storage → StatefulSet; one per node → DaemonSet; finite task → Job; scheduled task → CronJob.

The Pod Lifecycle, Briefly

A pod moves through phases you’ll see in kubectl get pods:

  • Pending: created but not yet running, waiting to be scheduled, or pulling the image.
  • Running: scheduled to a node, containers started. (Being “Running” doesn’t mean “ready to serve”, that’s what readiness probes are for, Lesson 9.)
  • Succeeded / Failed: for Jobs, the containers finished (0 exit = Succeeded, non-zero = Failed).
  • CrashLoopBackOff (a status you’ll see often): the container keeps crashing and restarting, with Kubernetes waiting longer between attempts. It almost always means a bug, a missing config, or hitting a limit (Lesson 5). kubectl logs and kubectl describe tell you why.

When you delete a pod (or a rolling update replaces it), Kubernetes sends it a termination signal and gives it a grace period to shut down cleanly (terminationGracePeriodSeconds, default 30) before forcing it. Your app should handle that signal, finish in-flight requests, and exit, important for zero-downtime deploys (Lesson 4).

Pitfalls to Avoid

  1. Thinking the container is the unit. The pod is; it’s what gets scheduled, healed and scaled.
  2. Putting unrelated services in one pod. They’d share a node, scale together and die together. Use separate pods.
  3. Creating bare Pods in production. No self-healing above the node, no rollout. Use a controller.
  4. Expecting two containers in a pod to use the same port. They share the network namespace; ports can’t collide.
  5. Using a Deployment for a database. Stateful systems need a StatefulSet’s stable identity and storage (Lesson 11).
  6. Treating “Running” as “ready.” A pod can be Running but not yet able to serve; that’s readiness (Lesson 9).
  7. Ignoring graceful shutdown. Not handling the termination signal drops in-flight requests during deploys.

Quick Recap

  • A pod is one or more containers sharing a network address and lifecycle, scheduled together on one node.
  • Most pods have one container; a second is a sidecar (proxy, log shipper, config reloader) that supports the main one.
  • You rarely create pods directly, a bare Pod has no self-healing above the node. Use a controller.
  • Deployment for stateless, StatefulSet for identity/storage, DaemonSet for per-node, Job/CronJob for finite/scheduled work.
  • Watch for CrashLoopBackOff (a crashing container) and handle graceful shutdown for zero-downtime deploys.

Next lesson: the controller you’ll use most. Deployments, how they manage ReplicaSets to give zero-downtime rolling updates, and how rollback is instant.