Understanding Kubernetes: Pods Are Not Containers
After talking about Kubernetes as an API-driven reconciliation system in the previous article, it’s time to look at one of the most fundamental Kubernetes objects: Pods.
When starting with Kubernetes, it’s very easy to think something like: “A Pod is basically just a container.” And to be fair, Kubernetes tutorials accidentally reinforce this idea all the time.
Most examples look something like this:
containers:
- name: nginx
image: nginx
One Pod. One container.
So naturally, Pod and container start feeling interchangeable. But internally, Kubernetes treats them very differently. And once you start looking at topics like:
- networking
- sidecars
- init containers
- shared storage
- service meshes
the distinction suddenly becomes extremely important. Because Pods are not containers. Pods are execution environments around containers.
Pods are Kubernetes objects
Just like Deployments or Services, Pods are API objects stored in the Kubernetes API server. For example:
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
spec:
containers:
- name: nginx
image: nginx
Again, this is not Kubernetes “running a container”. This is you declaring a desired state object in the API. And Kubernetes then reconciles reality to match it.
One important detail here: Kubernetes never schedules containers directly. It always schedules Pods. That’s why Pods become the smallest deployable unit in Kubernetes.
Containers are not
While Pods are first-calls Kubernetes API objects, containers themselves only exists as part of a Pod specification. This helps a lot when it comes to what containers often need, e.g.:
- communicate closely
- share storage
- share networking
- start in a certain order
- behave like one logical unit
A Pod in Kubernetes hence becomes a shared execution environment for one or more containers living in one namespace.
This means containers inside one Pod:
- share the same IP address
- share the same network stack
- communicate via localhost
- share port space
For example:
Pod
├── nginx container
└── sidecar container
Both containers can reach each other via localhost because Kubernetes places them into the same network namespace. This is also why two containers inside one Pod cannot both bind to the same port.
And importantly: The Pod itself is not:
- a VM
- a machine
- a durable object
It’s simply the environment wrapping the containers.
At this point, many people ask: “If Pods can contain multiple containers, why do most examples only use one?” And the answer is actually pretty simple: Most applications simply do not need tightly coupled helper containers.
So even though Kubernetes always wraps containers inside Pods, many Pods still only contain one container. But multi-container Pods become very useful once containers need to work together closely.
Sidecars: helper containers inside a Pod
One common example is the sidecar pattern. This usually means we have one main application container with one helper container running next to it.
These sidecars could be logging agents, proxies, service mesh sidecars, or synchronization helpers, sharing networking and livecycle behaviour with the main application container. For example:
- a proxy sidecar can intercept local traffic
- a logging sidecar can access shared volumes
- a service mesh sidecar can transparently handle networking
Init containers
Another special Pod concept are init containers. These run before the normal application containers start. Typical use cases include:
- waiting for dependencies
- database migrations
- preparing configuration
- downloading assets
Only when all init containers successfully finish, Kubernetes will start the main containers.
Demo time
One of the easiest ways to see Pod behavior in practice is running two containers inside one Pod. For example:
apiVersion: v1
kind: Pod
metadata:
name: multi-container-demo
spec:
containers:
- name: nginx
image: nginx
- name: toolbox
image: busybox
command: ["sleep", "3600"]
After creating the Pod with
kubectl apply -f pod.yaml
we can open a shell inside the busybox container:
kubectl exec -it multi-container-demo -c toolbox -- sh
and from there run:
wget -qO- localhost
Even though nginx runs in a different container, it is reachable via localhost because both containers share the same Pod network namespace.
Pods are designed to be disposable
Another very important mindset shift: Pods are intentionally ephemeral objects.
Kubernetes does not treat Pods like durable machines.. Instead, Pods are expected to:
- disappear
- be recreated
- move between nodes
- receive new IP addresses
all the time.
And this directly connects back to the reconciliation model from the previous article. If a Pod disappears, a controller simply creates a new Pod object matching the desired state.
And that distinction matters a lot, because it changes how applications need to behave in Kubernetes:
- state should usually live outside Pods
- workloads should tolerate restarts
- applications should handle rescheduling gracefully
Pod lifecycle
Pods move through different phases during their lifecycle:
- Pending
- Running
- Succeeded
- Failed
You can inspect this with kubectl get pods
Useful Pod debugging commands
When starting with Kubernetes, these commands become incredibly useful:
kubectl get pods
kubectl get pods -o wide
kubectl describe pod <name>
kubectl logs <name>
kubectl exec -it <name> -- sh
especially when debugging workloads.
Summing up
Pods are a Kubernetes abstraction around containers. They provide shared networking, shared storage, shared lifecycle behavior, and shared execution context.
Understanding this distinction becomes especially important once you start working with networking, observability, service meshes, scheduling, and security boundaries.
This was the second part of my Kubernetes series. Next up: Deployments and why Pods almost never run alone :)
