Kubernetes Pods Explained: What a Pod Is, How It Works, and When to Use One
A Kubernetes pod is the smallest deployable unit in the system. Every container you run in a cluster runs inside a pod. Understanding pods is the foundation for understanding how Kubernetes schedules, runs, and recovers workloads.
In plain English, what is a pod?
A pod is a thin wrapper that holds one or more containers and gives them a shared environment to run in. When Kubernetes needs to run your application, it does not schedule the container directly. It schedules a pod that contains the container. The pod lands on a node, the node starts the containers inside it, and Kubernetes treats the pod as the unit of work from that point on.
In most cases a pod holds exactly one container: your application. Multi-container pods exist for specific patterns covered below, but single-container pods are the everyday default.
Think of a pod as a shared flat. Each container is a flatmate with their own bedroom (process space), but they all share the same front door (IP address) and kitchen (shared volumes). They knock on the door next door — talk over localhost — rather than calling across town. Kubernetes does not care about individual flatmates; it only deals with the flat as a whole.
A pod is not the same thing as:
- A container. A container is the application process. A pod is the Kubernetes envelope around it.
- A node. A node is the VM that runs pods. One node can run many pods.
- A Deployment. A Deployment is a controller that creates and manages pods. The pod is what actually runs; the Deployment is what keeps it healthy.
What a Kubernetes pod is
A pod is defined by a few core properties that set it apart from a standalone container:
Smallest deployable unit. You cannot schedule a bare container in Kubernetes. The pod is the minimum schedulable object.
Wraps one or more containers. All containers in a pod are defined in one spec and managed as one unit.
Shared network namespace. All containers in a pod share the same IP address and port space. They communicate with each other over
localhost. Each pod gets its own cluster-internal IP.Shared storage. Containers in a pod can mount the same volumes, letting them read and write shared files.
Co-scheduled on the same node. All containers in a pod always land on the same node. They start and stop together.
Ephemeral by design. Pods are not meant to be long-lived. They can be deleted, rescheduled, or replaced at any time. Persistent state belongs in a volume or external storage, not inside the pod.
How Kubernetes pods work
When you submit a pod spec to Kubernetes, this sequence happens:
The API server accepts the pod spec and stores it in etcd. The pod is in the Pending phase.
The scheduler watches for unscheduled pods. It evaluates node capacity, resource requests, affinity rules, and taints, then assigns the pod to a suitable node.
The kubelet on that node picks up the assignment. It pulls the container images, starts the containers, and begins running health checks.
Containers in the same pod share a network namespace. They all live at the same IP address and can call each other over
localhost.If a container crashes, the kubelet restarts it based on the pod’s
restartPolicy(default:Always). If the container keeps crashing, the pod entersCrashLoopBackOff.
In practice you rarely write a raw pod spec. A Deployment embeds a pod template and handles creating, replacing, and updating pods for you. Understanding the pod spec is still essential because everything in a Deployment’s spec.template is a pod spec.
# simple-pod.yaml — a standalone pod (useful for learning and debugging)
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: default
labels:
app: my-app
spec:
containers:
- name: app
image: us-central1-docker.pkg.dev/my-project/my-repo/my-app:v1
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
- name: APP_ENV
value: "production"# Apply the manifest
kubectl apply -f simple-pod.yaml
# List pods in the default namespace
kubectl get pods
# Get full details: events, conditions, container statuses
kubectl describe pod my-app
# View container logs
kubectl logs my-app
# View logs from the previous (crashed) container instance
kubectl logs my-app --previous
# Open a shell inside the running container
kubectl exec -it my-app -- bash
# Delete the pod
kubectl delete pod my-appAlways set resource requests and limits. Requests tell the scheduler how much CPU and memory to reserve. Without them the scheduler cannot make good placement decisions, the HorizontalPodAutoscaler cannot calculate CPU utilisation, and pods can land on already-saturated nodes. On GKE Autopilot, pods without requests are rejected outright.
Pod lifecycle
Every pod moves through a defined set of phases from creation to termination:
| Phase | What it means |
|---|---|
| Pending | Accepted by the cluster but containers have not started. May be waiting for scheduling, image pull, or resource availability. |
| Running | Bound to a node; at least one container is running or starting. |
| Succeeded | All containers exited with code 0 and will not be restarted. Typical for batch jobs. |
| Failed | All containers have terminated; at least one exited with a non-zero code. |
| Unknown | State cannot be determined, usually a node communication failure. |
CrashLoopBackOff
CrashLoopBackOff is not a phase. It is a container restart status that appears when Kubernetes keeps restarting a container because it keeps crashing. Restart delays increase exponentially: 10s, 20s, 40s, up to 5 minutes per cycle.
The most common debugging mistake: running kubectl logs my-pod after a crash shows the new container’s logs, which are usually empty. The crash output is in the previous instance. Always run kubectl logs my-pod —previous first.
Common causes of CrashLoopBackOff:
- The app exits with an error immediately on startup (missing config, bad arguments)
- A required environment variable, config file, or Secret is missing. See Managing Secrets in Kubernetes
- The container is killed by OOM because memory limits are too low
- The entrypoint or command in the image is incorrect
What to check first
# Step 1: Logs from the PREVIOUS container instance
kubectl logs my-app --previous
# Step 2: Events and container state
kubectl describe pod my-app
# Step 3: Current pod status and node placement
kubectl get pod my-app -o wide
# Watch pod status changes in real time
kubectl get pods --watchContainer restart behaviour is controlled by spec.restartPolicy:
Always(default) — restart on any termination. Use for long-running services.OnFailure— restart only on non-zero exit codes. Use for batch jobs.Never— do not restart. Use for one-shot tasks where you want to inspect the result.
Pod networking
Every pod gets its own IP address from the cluster’s pod CIDR range. On GKE VPC-native clusters (the default since GKE 1.21), pod IPs are real VPC IP addresses, reachable from other VMs in the same VPC without NAT or tunnelling. See GKE Networking Model for how alias IP ranges make this work.
Containers in the same pod communicate via
localhost, with no network hop.Pods across the cluster communicate using pod IPs with no NAT required within the cluster.
Pod IPs are ephemeral. They change when a pod is rescheduled, restarted, or replaced. Never hardcode a pod IP.
DNS within the cluster follows the pattern
service-name.namespace.svc.cluster.local.
Use a Service to get a stable virtual IP and DNS name that load-balances across pods as they come and go. For traffic coming in from outside the cluster, you need a Service of type LoadBalancer or an Ingress controller.
# Get a pod's IP address
kubectl get pod my-app -o jsonpath='{.status.podIP}'
# Port-forward to your local machine for debugging
kubectl port-forward pod/my-app 8080:8080
# Then in another terminal:
curl http://localhost:8080Multi-container pods
Most pods run a single container. Multi-container pods are used for two specific patterns where containers need to be tightly coupled on the same node with shared network and storage access.
Sidecar pattern
A sidecar container runs alongside the main container and handles supporting concerns: log shipping, metrics collection, or a service mesh proxy like Envoy. Because the sidecar shares the same network namespace and volumes, it can tail log files or intercept traffic without any changes to the main application code.
spec:
containers:
- name: app
image: my-app:v1
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-shipper
image: fluent/fluentd:v1.16
volumeMounts:
- name: logs
mountPath: /var/log/app
readOnly: true
volumes:
- name: logs
emptyDir: {}Init containers
Init containers run to completion before the main application container starts. Use them for setup tasks: waiting for a database to become ready, fetching configuration from a remote source, or seeding a shared volume.
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c',
'until nc -z postgres-service 5432; do echo waiting for db; sleep 2; done']
containers:
- name: app
image: my-app:v1Init containers run sequentially. Each must complete successfully before the next one starts, and before any main containers start. If an init container fails, Kubernetes retries it according to the pod’s restart policy. This makes them a reliable way to gate startup on external dependencies.
When to use pods
Every workload in Kubernetes uses pods. The question is not whether to use pods, but which controller should create them for you.
| What you’re running | Controller to use |
|---|---|
| Stateless web app, API, or worker | Deployment |
| Database, message queue, or anything needing stable identity | StatefulSet |
| Cluster-level agent (log shipper, monitoring, security tool) | DaemonSet |
| One-time batch task | Job |
| Recurring scheduled task | CronJob |
You create a pod directly only when learning, testing, or debugging a specific node. In all other cases, you define a pod template inside one of the controllers above, and the controller handles creating, replacing, and scaling pods for you.
When deploying to GKE for the first time, deploying containers with kubectl walks through getting your first pods running. Once things work, move to Deployment manifests for anything you want to keep running reliably.
Pod vs container vs Deployment
These three concepts are closely related but operate at different levels:
| Concept | What it is | Created by |
|---|---|---|
| Container | A running application process packaged with its dependencies (OS libs, runtime, config). Defined by an image. | Docker / containerd |
| Pod | A Kubernetes object that wraps one or more containers, gives them a shared IP and optional shared volumes, and places them on a single node. | You (or a controller) via kubectl apply |
| Deployment | A Kubernetes controller that manages a set of identical pods. Maintains the desired replica count, replaces failed pods, and handles rolling updates. | You via kubectl apply |
The mental model: a container is what runs, a pod is how Kubernetes runs it on a node, and a Deployment is how Kubernetes keeps the right number of pods running over time.
A container is a employee. A pod is their office desk — the physical spot they work from, with shared equipment like the phone line and filing cabinet. A Deployment is the HR policy that says “we always need five people at desks in this department” and handles replacing anyone who leaves.
A pod created without a controller is called a naked pod. If the node it runs on fails, the pod is gone with no replacement. A pod managed by a Deployment that disappears is automatically replaced. This is why you should almost never create naked pods in production.
Common mistakes
Running
kubectl logs PODafter a crash without—previous. After a crash, Kubernetes starts a new container immediately. The new container’s logs are empty. The crash output lives in the previous container instance. Always runkubectl logs my-pod —previousfirst.Creating naked pods instead of Deployments. A pod without a controller is not self-healing. Node failure, pod eviction, or a crash means the pod is gone permanently. Use a Deployment so Kubernetes maintains your desired replica count automatically.
Not setting resource requests. Without CPU and memory requests, the scheduler cannot make good placement decisions. Pods without requests can land on saturated nodes, cause OOM kills on neighbouring pods, and break HPA autoscaling. On GKE Autopilot, pods without requests are rejected outright.
Hardcoding pod IPs. Pod IPs change every time a pod is rescheduled or restarted. Any code or config that stores a pod IP will eventually break. Use a Service to get a stable virtual IP and DNS name.
Putting secret values directly in pod environment variables. Plain environment variables in the pod spec are visible to anyone who can read the manifest. Store sensitive values in Kubernetes Secrets and reference them using
envFromorvalueFrom.secretKeyRef.
Summary
- A pod is the smallest deployable unit in Kubernetes. It wraps one or more containers that share a network namespace and optional storage volumes.
- You do not schedule containers directly. The scheduler places pods onto nodes and the kubelet starts containers inside them.
- Containers in the same pod share an IP and communicate over localhost. Each pod’s IP is ephemeral.
- Pod lifecycle phases: Pending, Running, Succeeded, Failed, Unknown. CrashLoopBackOff is a container restart status, not a phase.
- When debugging a crash, check
kubectl logs POD —previousfirst, thenkubectl describe pod POD. - In production, manage pods through Deployments, StatefulSets, DaemonSets, or Jobs. Never create naked pods for production workloads.
- Use a Service for stable access to pods. Never rely on pod IPs directly.
Frequently asked questions
What is a Kubernetes pod in simple terms?
A pod is the smallest deployable unit in Kubernetes. It wraps one or more containers that share the same IP address, port space, and optional storage volumes. You do not schedule containers directly in Kubernetes — you schedule pods. In practice, most pods contain a single container, and most pods are created by a Deployment rather than by hand.
What is the difference between a pod and a container?
A container is a running application process packaged with its dependencies. A pod is the Kubernetes abstraction that runs one or more containers together on the same node, giving them a shared IP address and the ability to share volumes. The container is the app; the pod is the envelope Kubernetes uses to schedule and manage it.
What is the difference between a pod and a Deployment?
A pod is a single scheduled unit. If it crashes or the node fails, nothing replaces it automatically. A Deployment is a controller that manages a set of identical pod replicas. It watches the desired replica count, replaces failed pods, and handles rolling updates. In production, you almost always use a Deployment instead of creating pods directly.
What does CrashLoopBackOff mean?
CrashLoopBackOff means a container is starting, crashing, and being restarted by Kubernetes in a loop. The BackOff part means Kubernetes adds increasing delays (10s, 20s, 40s, up to 5 minutes) between restart attempts. Common causes: the app exits with an error on startup, a required environment variable or config file is missing, or the container is killed by OOM. Check kubectl logs POD_NAME --previous to see the crash output from the last container instance.
What are the phases of a pod lifecycle?
A pod moves through five phases: Pending (accepted but containers not yet running), Running (at least one container is running), Succeeded (all containers exited with code 0), Failed (all containers terminated and at least one exited with a non-zero code), and Unknown (state cannot be determined, usually a node communication failure). CrashLoopBackOff is not a phase — it is a container restart status that appears within a Running or Failed pod.
Do pods have stable IP addresses?
No. Pod IPs are ephemeral — they change every time a pod is rescheduled or restarted. Never store a pod IP as a stable address in code or configuration. Use a Kubernetes Service instead. A Service gives you a stable virtual IP and DNS name that load-balances across all matching pods, even as they come and go.
Why do pods exist instead of just scheduling containers directly?
Pods provide a co-location abstraction. Containers in the same pod are always scheduled on the same node, share a network namespace (same IP), and can share mounted volumes. This lets you run tightly coupled processes without needing them to communicate over the network. The pod is also the unit Kubernetes uses for health checking, resource accounting, and scheduling.