What Is a Pod?
When you run docker run nginx, Docker creates a container. It's tempting to assume Kubernetes does the same — but Kubernetes never creates containers directly. It creates a Pod, and the Pod contains the container.
Officially, a Pod is the smallest deployable unit in Kubernetes. In practical terms, a Pod is a wrapper around one or more containers — the smallest object that Kubernetes can create, schedule, monitor, and manage.
Why Not Just Containers?
Docker's model is simple: Docker runs a container, and that's the whole story. Kubernetes operates at a much larger scale, and every workload it runs needs things a bare container can't provide on its own:
- Networking
- Storage
- Monitoring
- Scheduling
- Health checks
- Restart policies
So Kubernetes wraps containers inside a Pod, and the Pod supplies the network identity, storage, lifecycle, and shared resources that the containers need:
Kubernetes → Pod → Container
Think of the Pod as a protective wrapper around one or more containers.
Pod Architecture
Most Pods hold a single container:
┌──────────── Pod ────────────┐
│ │
│ nginx container │
│ │
└─────────────────────────────┘
A Pod can also hold multiple tightly coupled containers — for example, an application container alongside a log agent and a metrics exporter:
┌──────────── Pod ────────────┐
│ nginx │
│ log-agent │
│ metrics-exporter │
└─────────────────────────────┘
Containers inside the same Pod share the network namespace (they can reach each other over localhost), share volumes, and can communicate with each other very quickly.
The Pod Manifest
Here is the simplest useful Pod manifest — one Pod running one nginx container:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80This YAML tells Kubernetes: "Create one Pod containing one nginx container." Every Kubernetes manifest you'll ever write follows the same top-level shape, so it's worth understanding each field precisely.
| Field | Purpose |
|---|---|
apiVersion | Which Kubernetes API should handle this object |
kind | What type of object is being created |
metadata | Information about the object (name, namespace, labels) |
spec | The desired state — what the object should look like |
apiVersion
Kubernetes has many APIs, and apiVersion tells the cluster which one should interpret this object. Pods live in the core v1 API; other resources belong to different API groups:
| Resource | API Version |
|---|---|
| Pod | v1 |
| Service | v1 |
| ConfigMap | v1 |
| Secret | v1 |
| Deployment | apps/v1 |
| ReplicaSet | apps/v1 |
kind
kind answers the question "what object am I creating?" — Pod, Service, Deployment, ReplicaSet, Secret, and so on.
metadata
Metadata is the identity card of the Pod. It describes the object but does not define how the application runs. name is required; namespaces, labels, and annotations are common additions:
metadata:
name: nginx-pod
namespace: dev
labels:
app: nginx
annotations:
owner: venkatThe name here is the Pod name — the value you'll see later in kubectl get pods output.
spec
spec is the most important section: it declares the desired state. You're telling Kubernetes, "I want my Pod to look like this," and Kubernetes works to make it so. Everything that defines how the application runs lives under spec — containers, volumes, restart policy, resources, environment variables, security context.
containers
containers is a list, because a Pod can run one container or several that work together. Each entry needs at minimum a name and an image.
name (container)
name: nginxThis is the container name, not the Pod name. The Pod nginx-pod contains a container named nginx — two different identifiers at two different levels.
image
image: nginx:latestThis is the container image Kubernetes pulls — by default from Docker Hub — onto whichever worker node the Pod is scheduled to:
Kubernetes → Docker Hub → nginx:latest → worker node → container
An image name without a tag (nginx) implicitly means latest. In production, always pin a fixed version instead:
image: nginx:1.27Never deploy latest to production — you lose control over exactly which version is running.
ports and containerPort
ports:
- containerPort: 80containerPort declares which port the application inside the container listens on.
containerPort does not expose the application. Many beginners assume that setting it makes the app reachable from a browser — it doesn't. It simply documents "this application listens on port 80." To actually route traffic to a Pod, you need a Service, covered in a later lesson.
Creating the Pod
Save the manifest as pod.yaml and apply it:
kubectl apply -f pod.yamlpod/nginx-pod created
Verify it's running:
kubectl get podsNAME READY STATUS
nginx-pod 1/1 Running
The output columns are worth knowing, since you'll read them constantly:
| Column | Meaning |
|---|---|
| NAME | Pod name |
| READY | Ready containers / total containers |
| STATUS | Running, Pending, CrashLoopBackOff, etc. |
| RESTARTS | Number of restarts |
| AGE | Time since creation |
Inspecting the Pod
kubectl describe shows the full picture of a Pod — events, IP address, image, labels, the node it's scheduled on, and its conditions. It's the first tool to reach for when debugging:
kubectl describe pod nginx-podTo see the container's logs:
kubectl logs nginx-podTo open a shell inside the running container:
kubectl exec -it nginx-pod -- bashIf the image doesn't include bash, use sh instead:
kubectl exec -it nginx-pod -- shOnce inside, you're in the container's filesystem — try ls, pwd, or cat /etc/os-release, then exit to return to your machine.
Deleting the Pod
kubectl delete pod nginx-podThe Pod disappears — and with it, your application. Who recreates it? Nobody. Kubernetes obeyed your command, and nothing in the cluster is watching to bring that Pod back. Keep this in mind; it's the key to the last section of this lesson.
A More Realistic Pod
The nginx example is deliberately minimal. A production-grade Pod spec typically adds environment variables (including values pulled from Secrets) and resource requests and limits:
# pod.yml
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
spec:
containers:
- name: app
image: my-org/my-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"The structure is identical to the simple example — same apiVersion, kind, metadata, spec, containers — just with more fields inside each container entry.
The Limitations of Bare Pods
A bare Pod runs your container, and that's all it does. Everything else is your problem:
- Manual creation
- Manual recovery
- No self-healing
- No scaling
- No rolling updates
- No rollback
- No high availability
Play out the failure scenarios. If the node running your Pod fails, the application is down. If someone runs kubectl delete pod nginx-pod, the application is down — and nothing recreates it. If you need 10 copies of the application, you must write and apply 10 Pod manifests by hand. None of this is practical for real workloads.
If one Pod dies, something should create another one — and that something should be Kubernetes itself. But a Pod cannot do that on its own. That is exactly why Kubernetes introduced the ReplicaSet: a controller that watches your Pods and automatically recreates them to maintain the desired count. That's the subject of the next lesson.