Skip to the document
Madhuopen lab
The Kubernetes Ninja PathTrack 1 — Kubernetes from the ground up

Chapter 3

Pods — The Smallest Deployable Unit

6 min read read1,637 wordsBeginner7 recall cards

Before you read, guess

What networking and storage capabilities allow multiple containers within a single Pod to communicate directly?

Take ten seconds and guess — even a wrong guess makes the answer stick. Tap to see where the chapter lands, or just read on.

A Pod can hold one or more containers that share the same IP address (same network namespace, reachable via localhost) and can share storage volumes.

In Post 2, you learned the kubelet runs containers because the API server tells it to. Here's the twist: the kubelet never actually runs a bare container. It runs something called a Pod. What is a Pod, and why does Kubernetes bother with this extra layer?

Why not just run containers?

Container = one packaged, runnable unit — your app plus everything it needs, zipped into a single image you can start anywhere. Containers are great on their own, but Kubernetes needed a slightly bigger building block, because some containers need to live and die together, share one network address, and share files on disk.

So Kubernetes invented the Pod. Pod = the smallest object you can create in Kubernetes — a thin wrapper around one or more containers. It guarantees three things: they run on the same node, they share the same IP address, and they can share storage volumes. You never schedule a bare container in Kubernetes — you always schedule a Pod, even though most Pods hold just one container.

Analogy: The Pod is an apartment. The containers inside are the roommates who live there. Most apartments hold one tenant — a single-container Pod. Sometimes a few roommates share one unit — a multi-container Pod. What they share: one street address (the Pod's IP — every roommate answers to it), shared utilities (the Pod's storage volumes), and they can talk down the hallway (localhost) instead of mailing a letter across town. The building manager — the node — doesn't rent rooms one at a time. The whole apartment is rented, occupied, and evicted as one unit. You don't keep one roommate and evict another; it empties out all together.

One IP address, shared by every container inside

Every Pod gets exactly one IP address, no matter how many containers live inside it. Two containers in the same Pod see the same network interface, so they can talk over localhost — the address a computer uses to talk to itself. It's just like two programs on your own laptop talking to each other: no service discovery, no DNS, nothing fancy needed.

Storage works the same way. A Pod can define one or more volumes — a storage location any container in the Pod can use. If Container A writes a log file to a shared volume, Container B can read that exact file at that exact path.

The sidecar pattern

When would you want more than one container in a Pod? The classic reason is the sidecar pattern. Sidecar = a small helper container riding along next to your main container, the way a motorcycle sidecar rides next to the motorcycle. Your main container writes logs to a file — say /var/log/app.log — but doesn't know how to ship them anywhere. So you add a small second container whose only job is to tail that file and forward it to your logging system. Both share the volume where the log lives, and both are deployed and destroyed together, as one Pod.

You'll see this same shape again later — for example, a service mesh proxy sitting next to your app container. For now, just remember the pattern: main container + helper container, same Pod, sharing network and storage.

Pods are mortal — and that's on purpose

Ephemeral = short-lived, not built to last. Pods are ephemeral on purpose. In production you almost never create one directly, because if a Pod dies — its node crashes, it gets evicted, or you delete it — nothing brings it back. A bare Pod has no self-healing behavior.

That's exactly the gap Deployments exist to fix — Post 4's topic. A Deployment watches a set of Pods and recreates them the moment they disappear, using the reconciliation loop from Post 1. But to understand what a Deployment manages, you first need to get your hands dirty with a raw Pod — so today we're deliberately doing it the "wrong" way, purely so the fundamentals stick.

Let's create a Pod

Imperative = you issue a direct command: "do this now." The fastest way to create a Pod is imperative — one kubectl command, no YAML needed.

$ kubectl run nginx --image=nginx
pod/nginx created

This tells the API server: "create a Pod named nginx, running the nginx image." The scheduler picks a node, and the kubelet there pulls the image and starts the container. Let's check on it.

$ kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          12s

READY 1/1 means one out of one containers in this Pod is ready. If this were a two-container sidecar Pod, you'd see 2/2 once both containers are up.

Want more detail, like which node it landed on and its IP address?

$ kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          40s   10.244.1.7    worker-1   <none>           <none>

There's that Pod IP we talked about — 10.244.1.7. Every container inside this Pod shares that exact address.

Digging deeper: describe, logs, exec

When something's wrong with a Pod, describe is your best friend — it shows events, container state, volumes, everything.

$ kubectl describe pod nginx
Name:         nginx
Namespace:    default
Node:         worker-1/192.168.49.2
Status:       Running
IP:           10.244.1.7
Containers:
  nginx:
    Image:          nginx
    State:          Running
    Ready:          True
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  50s   default-scheduler  Successfully assigned default/nginx to worker-1
  Normal  Pulled     48s   kubelet            Successfully pulled image "nginx"
  Normal  Created    48s   kubelet            Created container nginx
  Normal  Started    47s   kubelet            Started container nginx

That Events section is where you'll spend most of your debugging time in real clusters — it tells the story of everything that happened to this Pod, in order.

Want to see what the container prints to stdout — standard output, the normal text a running program prints?

$ kubectl logs nginx
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/09/03 10:15:02 [notice] 1#1: start worker processes

And to get an actual shell inside the running container, just like logging into a regular Linux box:

$ kubectl exec -it nginx -- /bin/bash
root@nginx:/#

Type exit to leave. (Some minimal images don't ship bash — try /bin/sh instead.) When you're done experimenting, clean up:

$ kubectl delete pod nginx
pod "nginx" deleted
Try it yourself: Create the nginx Pod, run kubectl get pods -o wide and note its IP. Then exec into it with kubectl exec -it nginx -- /bin/bash and run hostname -i from inside the container — it should match the Pod IP you just saw. That's the "one shared address" idea from the apartment analogy, proven with your own hands.

The declarative way: YAML manifests

kubectl run is imperative — you tell Kubernetes exactly what to do, right now. The declarative approach is different: YAML = a plain-text format for describing structured data. You write a YAML file describing the desired end state, and hand it to Kubernetes with kubectl apply -f. This should feel familiar from Post 1's reconciliation loop — you declare "this is what I want to exist," and Kubernetes makes it true.

Manifest = the YAML file that describes a Kubernetes object. Here's the minimal Pod manifest, equivalent to the command above:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx

Save that as pod.yaml and apply it:

$ kubectl apply -f pod.yaml
pod/nginx created

Every manifest needs four things: apiVersion (which version of the Kubernetes API this object belongs to), kind (Pod, Deployment, Service, and so on), metadata (name and labels), and spec (the desired state — here, the list of containers). You'll type this same skeleton hundreds of times, so get comfortable with it now.

In production, declarative YAML checked into version control is the standard — reviewable, repeatable, reproducible. Imperative commands are for quick experiments, debugging, and — as you're about to see — saving serious time on the CKA exam.

Exam trap: The CKA exam is timed, and typing full YAML by hand is slow and error-prone. The trick every fast test-taker uses is generating a YAML skeleton with an imperative command plus --dry-run=client -o yaml, then editing only what's needed: kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml. This creates nothing on the cluster — --dry-run=client means "show me what would be sent, don't send it" — it just prints valid YAML. You open the file, tweak a field or two, and kubectl apply -f it. This works for Deployments, Services, and more, and you'll lean on it constantly in later posts. Learn it now — it saves real minutes on exam day.

Key Takeaways

  • Kubernetes never schedules a bare container — it always schedules a Pod, the smallest deployable unit.
  • A Pod can hold one or more containers that share the same IP address (same network namespace, reachable via localhost) and can share storage volumes.
  • Multi-container Pods commonly follow the sidecar pattern — a main container paired with a small helper container, like a logging shipper, sharing a volume.
  • Pods are ephemeral and have no self-healing behavior on their own; in production, higher-level objects like Deployments (Post 4) manage Pods for you.
  • kubectl run is the fast, imperative way to create a Pod; a YAML manifest applied with kubectl apply -f is the declarative way — both matter for the exam.
  • kubectl run <name> --image=<image> --dry-run=client -o yaml generates a YAML skeleton instantly without creating anything — a critical time-saver for the CKA exam.
  • Core debugging commands: kubectl get pods -o wide (status and IP), kubectl describe pod (events and details), kubectl logs (container output), kubectl exec -it (shell inside a container).

Next up: Deployments — how Kubernetes keeps your Pods alive, replaces them when they die, and rolls out updates without downtime.

Before you go

In one sentence, what was this chapter about?

From memory, without scrolling up. Writing it is what makes it yours; the grade is only to show you what you had.

How sure?