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.
+------------------------------------------+| 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.
+------------------------------------------+| 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
+------------------------------------------+| 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:
+------------------------------------------+| 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.
apiVersion: v1kind: Podmetadata: name: payment-api namespace: production labels: app: payment-api # Labels are how Services find this pod version: v3.1.0 team: paymentsspec: # -- 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
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 themInit 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.
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: 8080Why 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.
+------------------------------------------+| 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
# Create a pod from a YAML filekubectl apply -f pod.yaml # List all pods in a namespacekubectl get pods -n production # List pods with their node assignment and IP addresseskubectl 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 podkubectl describe pod payment-api-xyz -n production# Shows: events, conditions, container statuses, volumes, resource usage # Get pod logskubectl logs payment-api-xyz -n production # Follow logs in real timekubectl logs -f payment-api-xyz -n production # Get logs from a specific container in a multi-container podkubectl logs payment-api-xyz -c log-shipper -n production # Get logs from the PREVIOUS crashed container instancekubectl logs payment-api-xyz -n production --previous # Execute a command inside a running containerkubectl exec -it payment-api-xyz -n production -- sh # Run a one-off debug pod and auto-delete it after exitingkubectl run debug \ --image=busybox:1.35 \ -it --rm \ -n production \ -- shPod Conditions — What They Mean
# Check pod conditionskubectl 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 = TrueCommon 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> |
RememberA pod getting an IP address does not mean it is ready. A pod is only ready when its
readinessProbepasses. Until then, the Service will not route traffic to it. Always check the Ready column —1/1means ready,0/1means the container is running but the readiness probe is failing.
TipUse
kubectl run debug --image=busybox -it --rm -- shto 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 MistakeCreating pods directly with
kubectl apply -f pod.yamlin 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.
SecurityEvery 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, setautomountServiceAccountToken: falsein 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.