Skip to main content

Pod

The smallest deployable unit in Kubernetes — a group of one or more containers that share the same network namespace, storage volumes, and lifecycle. Every container in Kubernetes runs inside a pod, and every pod gets its own unique IP address inside the cluster.

Pod — The Building Block of Everything in Kubernetes

What is a Pod in Simple Terms?

Before Kubernetes, you ran Docker containers directly. In Kubernetes, you never run a container directly — you always run a Pod, which wraps the container. Think of a Pod as a small wrapper that says: here is my container, here is the network it uses, here is the storage it needs, and here is how healthy it should stay.

Every single workload in Kubernetes — whether it is a web server, a database, a batch job, or a background worker — runs as a pod.

◈ DIAGRAM
+------------------------------------------+
| Pod IP: 10.244.1.15 |
| |
| +------------------+ |
| | Container | (your app) |
| | nginx:1.25 | |
| | port 80 | |
| +------------------+ |
| |
| Shared storage volumes |
| Shared network namespace |
| Shared lifecycle |
+------------------------------------------+
|
| Scheduled onto
v
+------------------------------------------+
| Worker Node (mumbai-prod-node-1) |
+------------------------------------------+

Why Pods Exist — The Problem They Solve

Some applications need two processes to run together and share resources. A web server and a log shipping agent for example. They need to share the same log files on disk. Running them as separate Docker containers on separate IPs makes sharing files complicated.

A Pod solves this — put both containers in the same pod and they automatically share the same filesystem volumes and the same localhost network. The log agent reads files written by the web server directly.

◈ DIAGRAM
+------------------------------------------+
| Pod (10.244.1.15) |
| |
| +------------------+ |
| | nginx container | writes logs |
| | port 80 | to /var/log |
| +------------------+ |
| | |
| | shared volume (/var/log) |
| v |
| +------------------+ |
| | fluentd sidecar | reads logs |
| | (log shipper) | from /var/log |
| +------------------+ |
| |
| Both containers talk via localhost |
| Both share the same IP address |
+------------------------------------------+

Pod Lifecycle — What Happens From Creation to Deletion

◈ DIAGRAM
+------------------------------------------+
| Pending |
| Pod accepted, waiting for a node |
| Containers not started yet |
| Image being pulled, resources reserved |
+------------------------------------------+
|
v
+------------------------------------------+
| Running |
| Pod is on a node |
| At least 1 container is actively running |
+------------------------------------------+
|
+---------+---------+
| |
v v
+------------------+ +------------------+
| Succeeded | | Failed |
| All containers | | At least one |
| exited with 0 | | container exited |
| (batch jobs) | | with non-zero |
+------------------+ +------------------+

There is also a special state:

◈ DIAGRAM
+------------------------------------------+
| Unknown |
| Node lost contact with API server |
| Node is likely down or unreachable |
+------------------------------------------+

A Complete Pod Specification Explained

You rarely create pods directly — you use Deployments. But understanding the full pod spec is essential because Deployments wrap this exact structure.

YAML
apiVersion: v1
kind: Pod
metadata:
name: payment-api
namespace: production
labels:
app: payment-api # Labels are how Services find this pod
version: v3.1.0
team: payments
spec:
# -- Container Definition --------------------------
containers:
- name: payment-api
image: registry.razorpay.in/payment-api:v3.1.0
ports:
- containerPort: 8080
name: http
# Environment variables
env:
- name: NODE_ENV
value: "production"
- name: DB_HOST
value: "10.0.1.50"
- name: DB_PASSWORD # Injected from a Secret — never hardcode passwords
valueFrom:
secretKeyRef:
name: db-credentials
key: password
# Resource requests and limits
resources:
requests:
cpu: "250m" # Scheduler reserves this on the node
memory: "256Mi" # Scheduler reserves this on the node
limits:
cpu: "1000m" # Container throttled if exceeded
memory: "512Mi" # Container killed (OOMKilled) if exceeded
# Health checks
readinessProbe: # Is this pod ready to receive traffic?
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10 # Wait 10s before first check
periodSeconds: 5 # Check every 5 seconds
failureThreshold: 3 # Remove from Service after 3 failures
livenessProbe: # Should Kubernetes restart this container?
httpGet:
path: /health
port: 8080
initialDelaySeconds: 20
periodSeconds: 15
failureThreshold: 3 # Restart container after 3 consecutive failures
# Volume mounts — where volumes appear inside the container
volumeMounts:
- name: config-vol
mountPath: /app/config
- name: tmp-dir
mountPath: /tmp
# -- Volumes ---------------------------------------
volumes:
- name: config-vol
configMap:
name: payment-api-config # Contents of this ConfigMap appear at /app/config
- name: tmp-dir
emptyDir: {} # Temporary scratch space, wiped when pod dies
# -- Scheduling Controls ---------------------------
serviceAccountName: payment-processor # RBAC identity for this pod
restartPolicy: Always # Always restart crashed containers
# (default for Deployments)

Multi-Container Pods — When to Use Them

Most pods have one container. Multi-container pods are used for specific patterns:

Sidecar Pattern — extending the main container

YAML
spec:
containers:
- name: api-server # Main container — the application
image: registry.swiggy.in/api:v2.1.0
ports:
- containerPort: 8080
- name: log-shipper # Sidecar — ships logs to Elasticsearch
image: fluent/fluent-bit:2.1
volumeMounts:
- name: log-vol
mountPath: /var/log/app
volumes:
- name: log-vol
emptyDir: {} # Both containers share this volume
# api-server writes logs, log-shipper reads them

Init Container Pattern — run setup before the main container starts

Init containers run and complete before the main container starts. Use them for database migrations, config file generation, or waiting for a dependency to be ready.

YAML
spec:
initContainers:
- name: wait-for-db # Must complete before main container starts
image: busybox:1.35
command:
- sh
- -c
- |
until nc -z postgres.production 5432; do
echo "Waiting for PostgreSQL..."
sleep 2
done
echo "PostgreSQL is ready!"
- name: run-migrations # Runs after wait-for-db completes
image: registry.razorpay.in/api:v3.1.0
command: ["node", "scripts/migrate.js"]
containers:
- name: api # Only starts after BOTH init containers finish
image: registry.razorpay.in/api:v3.1.0
ports:
- containerPort: 8080

Why You Should Almost Never Create Pods Directly

Pods created directly (without a Deployment) are mortal — if the pod crashes or the node it runs on dies, the pod is gone forever. Kubernetes does not replace it.

Bash
+------------------------------------------+
| Direct Pod (kubectl apply pod.yaml) |
| |
| Node dies -> Pod is GONE FOREVER |
| Pod crashes -> Pod is GONE FOREVER |
| Manual reapply required every time |
+------------------------------------------+
+------------------------------------------+
| Pod inside a Deployment |
| |
| Node dies -> New pod on another node |
| Pod crashes -> New pod started auto |
| Deployment always maintains desired count|
+------------------------------------------+

Only create bare pods for: one-off debugging sessions, short-lived test runs, or when learning Kubernetes. For everything in production, use a Deployment, StatefulSet, DaemonSet, or Job.

How to Run and Inspect Pods

Bash
# Create a pod from a YAML file
kubectl apply -f pod.yaml
# List all pods in a namespace
kubectl get pods -n production
# List pods with their node assignment and IP addresses
kubectl get pods -n production -o wide
# NAME READY STATUS NODE IP
# payment-api-xyz 1/1 Running mumbai-worker-1 10.244.1.15
# Watch pods in real time (useful during deployments)
kubectl get pods -n production --watch
# Get detailed information about a pod
kubectl describe pod payment-api-xyz -n production
# Shows: events, conditions, container statuses, volumes, resource usage
# Get pod logs
kubectl logs payment-api-xyz -n production
# Follow logs in real time
kubectl logs -f payment-api-xyz -n production
# Get logs from a specific container in a multi-container pod
kubectl logs payment-api-xyz -c log-shipper -n production
# Get logs from the PREVIOUS crashed container instance
kubectl logs payment-api-xyz -n production --previous
# Execute a command inside a running container
kubectl exec -it payment-api-xyz -n production -- sh
# Run a one-off debug pod and auto-delete it after exiting
kubectl run debug \
--image=busybox:1.35 \
-it --rm \
-n production \
-- sh

Pod Conditions — What They Mean

Bash
# Check pod conditions
kubectl describe pod payment-api-xyz -n production | grep -A 10 Conditions
# Output:
# Conditions:
# Type Status
# Initialized True <- init containers all completed
# Ready True <- readinessProbe is passing
# ContainersReady True <- all containers are running
# PodScheduled True <- node was found and pod was placed
# If Ready = False, the pod is running but NOT receiving traffic
# The Service will not route requests to it until Ready = True

Common Pod Status Values and What to Do

Status Meaning First Step
Pending No node assigned yet kubectl describe pod — check Events section for scheduling failures
ContainerCreating Node assigned, pulling image kubectl describe pod — check if image pull is failing
Running All containers running Normal — check readiness if traffic is not reaching it
CrashLoopBackOff Container keeps crashing kubectl logs --previous — read the crash output
OOMKilled Container exceeded memory limit kubectl top pod — measure usage and raise the memory limit
ImagePullBackOff Cannot pull the container image Check image name, tag, and registry credentials
Terminating Pod is being deleted Normal during rolling updates — stuck Terminating means a finalizer issue
Evicted Node ran out of resources Node memory or disk pressure — check kubectl describe node

Troubleshooting Reference

Problem Command
Pod stuck in Pending kubectl describe pod <name> -n <ns> — look at Events
Container keeps crashing kubectl logs <name> -n <ns> --previous
Pod running but not receiving traffic kubectl describe pod <name> — check Ready condition
Cannot connect to a pod kubectl exec -it <name> -- curl localhost:8080
Find which node a pod is on kubectl get pod <name> -o wide
See all pods on a specific node kubectl get pods -A --field-selector spec.nodeName=<node>
Check resource usage kubectl top pod <name> -n <ns>
Remember

A pod getting an IP address does not mean it is ready. A pod is only ready when its readinessProbe passes. Until then, the Service will not route traffic to it. Always check the Ready column — 1/1 means ready, 0/1 means the container is running but the readiness probe is failing.

Tip

Use kubectl run debug --image=busybox -it --rm -- sh to spin up a temporary debug pod inside the cluster. From inside this pod you can test DNS resolution, check connectivity to other services, and verify network policies — all from within the cluster network exactly as other pods experience it.

Common Mistake

Creating pods directly with kubectl apply -f pod.yaml in production. When the node dies, the pod is gone permanently. Always use a Deployment for stateless apps and a StatefulSet for stateful apps — these controllers automatically recreate pods when they are lost.

Security

Every pod automatically gets a ServiceAccount token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. Any code running inside the pod can use this token to call the Kubernetes API. If your application does not need to call the Kubernetes API, set automountServiceAccountToken: false in the pod spec to remove the token entirely — this prevents a compromised pod from being used to query cluster state.

Frequently Asked Questions

Why does Kubernetes wrap containers in pods instead of scheduling containers directly?

The pod abstraction exists because tightly coupled containers — like an app and a logging sidecar — need to share a network namespace and volumes, and be scheduled, scaled, and restarted together as one unit. A pod gives every container inside it the same IP and localhost, so they can communicate over loopback ports without service discovery. Kubernetes never schedules a bare container; the pod is the atomic scheduling unit, even for the common single-container case.

Why shouldn't you rely on a pod's IP address staying stable?

Pod IPs are ephemeral — when a pod restarts, gets rescheduled to another node, or is replaced during a rolling update, it gets a brand-new IP. Hardcoding a pod IP anywhere (configs, DNS entries, allowlists) breaks the moment that pod is recreated. Always address pods through a Service, which provides a stable virtual IP and DNS name that gets automatically updated to point at whichever pods are currently healthy.