Chapter 3
Pods — The Smallest Deployable Unit
Before you read, guessWhat 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.
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
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.
--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 runis the fast, imperative way to create a Pod; a YAML manifest applied withkubectl apply -fis the declarative way — both matter for the exam.kubectl run <name> --image=<image> --dry-run=client -o yamlgenerates 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).