Kubernetes Pods Explained

Pods are the fundamental building blocks in Kubernetes. Every workload in a cluster — every container you run — lives inside a pod. Understanding pods deeply means understanding how Kubernetes actually executes your applications, why things restart, and how to debug when they do not work.

What is a pod

A pod is a wrapper around one or more containers that share the same network namespace and storage volumes. Containers in the same pod communicate over localhost, share the same IP address, and can read the same mounted volumes.

Think of a pod as a logical host. Just as multiple processes on a VM share the same IP and filesystem, multiple containers in a pod share the same network and can share storage. The difference is that each container has its own isolated process space and filesystem root.

Most pods contain exactly one container. Multi-container pods exist for sidecar patterns — an additional container that enhances the main one — but they are the exception, not the rule.

The anatomy of a pod YAML

Here is a complete pod spec with the most important fields:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: default
  labels:
    app: my-app
    version: "1.0"
spec:
  containers:
  - name: app
    image: nginx:1.25
    ports:
    - containerPort: 80
    resources:
      requests:
        cpu: "100m"       # 100 millicores = 0.1 CPU
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "256Mi"
    env:
    - name: APP_ENV
      value: "production"
    livenessProbe:
      httpGet:
        path: /healthz
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 15
    readinessProbe:
      httpGet:
        path: /ready
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 10
  restartPolicy: Always

Key fields explained:

  • resources.requests — what the scheduler uses to decide where to place the pod. The node must have at least this much free.
  • resources.limits — the maximum the container can consume. Exceeding CPU limit causes throttling. Exceeding memory limit causes the container to be OOM-killed and restarted.
  • livenessProbe — Kubernetes checks this periodically. If it fails, the container is killed and restarted.
  • readinessProbe — Kubernetes checks this before sending traffic. A pod that fails readiness is removed from Service endpoints until it recovers.

Pod lifecycle and states

Every pod moves through phases during its life:

PhaseMeaningWhat to do
PendingPod accepted but not yet running. Waiting for scheduling or image pull.Check events with kubectl describe pod
RunningPod is bound to a node. At least one container is running.Normal state for long-running workloads
SucceededAll containers exited with status 0.Normal for batch/job pods
FailedAll containers have exited. At least one exited non-zero.Check container logs for error
UnknownCannot get pod status (node communication issue).Check node health

Beyond the phase, individual containers within a pod have their own status. A pod can be “Running” phase but have a container in “CrashLoopBackOff” state — meaning it keeps crashing and restarting.

Diagnosing CrashLoopBackOff — a real debugging walkthrough

CrashLoopBackOff is one of the most common states you will encounter. It means the container starts, crashes, and Kubernetes keeps trying to restart it with increasing delays.

Here is how to diagnose it systematically:

# Step 1: See the pod status
kubectl get pods
# NAME          READY   STATUS             RESTARTS   AGE
# my-app-xyz    0/1     CrashLoopBackOff   5          3m

# Step 2: Describe the pod for events
kubectl describe pod my-app-xyz
# Look for: Exit Code, Last State, Events section

# Step 3: Read the container logs (current run)
kubectl logs my-app-xyz

# Step 4: Read logs from the previous (crashed) run
kubectl logs my-app-xyz --previous

# Step 5: Check if resource limits are too low (OOMKilled)
kubectl describe pod my-app-xyz | grep -A5 "Last State"
# If Exit Code: 137 → OOM killed (increase memory limit)
# If Exit Code: 1   → application error (check logs)
# If Exit Code: 126 → permission error
# If Exit Code: 127 → command not found

The most useful combination is kubectl describe to see the exit code and events, then kubectl logs —previous to see what the container printed before it died.

Init containers

Init containers run to completion before the main container starts. They are useful for setup tasks like waiting for a database to be ready, downloading configuration files, or setting file permissions.

spec:
  initContainers:
  - name: wait-for-db
    image: busybox:1.36
    command: ['sh', '-c',
      'until nc -z postgres-service 5432; do echo waiting; sleep 2; done']
  containers:
  - name: app
    image: my-app:1.0

The init container uses netcat to check if the Postgres service is accepting connections. The main container does not start until the init container exits with 0. This prevents your app from starting before its dependencies are ready.

# Watch init container progress
kubectl get pods -w
# NAME       READY   STATUS     RESTARTS
# my-app     0/1     Init:0/1   0

kubectl logs my-app -c wait-for-db

The sidecar pattern

Multi-container pods are most often used for the sidecar pattern — adding a helper container that augments the main one without modifying it. Common sidecars:

  • Log shippers — a Fluentd or Fluent Bit container that reads log files written by the main container and forwards them to a logging backend
  • Service mesh proxies — Envoy or Linkerd proxies injected automatically by a service mesh to handle mTLS and traffic management
  • Configuration refreshers — a container that watches for configuration changes and signals the main app to reload
spec:
  containers:
  - name: app
    image: my-app:1.0
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
  - name: log-shipper
    image: fluent/fluent-bit:2.1
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
      readOnly: true
  volumes:
  - name: logs
    emptyDir: {}

Both containers mount the same emptyDir volume. The app writes logs to /var/log/app. The log shipper reads from the same directory and forwards them. Neither container has to know about the other’s existence.

Understanding CPU and memory management

CPU and memory work differently in Kubernetes, and the difference matters when things go wrong.

CPU is compressible. If a container tries to use more CPU than its limit, it gets throttled — slowed down. It does not crash. You may notice your application becomes sluggish but it keeps running.

Memory is not compressible. If a container exceeds its memory limit, the kernel OOM-kills it immediately. The container exits with code 137 and Kubernetes restarts it. This causes the CrashLoopBackOff cycle.

# Check what resources your cluster nodes have
kubectl describe nodes | grep -A5 "Allocated resources"

# Check what a pod is actually using (requires metrics-server)
kubectl top pod my-app-xyz

# Check node resource usage
kubectl top nodes
Note

If kubectl top returns “error: Metrics API not available”, the metrics-server addon is not installed. Enable it in AKS: az aks enable-addons —addons monitoring —resource-group my-rg —name my-cluster

Standalone pods vs managed pods

You can create a pod directly with kubectl apply -f pod.yaml. But standalone pods are almost never the right choice for production workloads. Here is why:

  • If a standalone pod’s node fails, the pod is gone forever. Kubernetes does not reschedule it.
  • You cannot update a standalone pod’s image with a rolling strategy.
  • You cannot scale a standalone pod.

Almost everything in production should use a Deployment (for stateless apps), StatefulSet (for stateful apps), DaemonSet (one pod per node), or Job/CronJob (batch tasks). These higher-level objects manage pods for you and add the reliability features that standalone pods lack.

Common mistakes

  1. No liveness or readiness probes. Without probes, Kubernetes cannot tell when your app is stuck or not ready. Traffic gets sent to pods that are not ready, and stuck containers are never restarted. Always define both probes for production services.
  2. Setting memory limits too low. If your app uses more memory than its limit during normal operation, it will be OOM-killed in a loop. Profile your app’s memory usage under realistic load before setting limits.
  3. No resource requests set. Pods without requests get scheduled based on node capacity alone. The scheduler cannot make good decisions, and nodes become overcommitted. Always set requests.
  4. Storing state in pod filesystem. Pod filesystem is ephemeral. When the pod restarts, all filesystem changes are lost. Use Persistent Volumes for any data that must survive restarts.
  5. Running containers as root. The default in many container images is to run as root (UID 0). This is a security risk. Add securityContext: runAsNonRoot: true and specify a non-root UID in your pod spec.

Frequently asked questions

Should I run multiple containers in one pod?

Only if the containers are tightly coupled and must share the same lifecycle, network, and storage. A web server and its log shipper sidecar is a valid multi-container pod. An app server and its database are not — keep those separate.

Why does a pod restart even when my container works fine?

Restarts happen when a container exits for any reason — including successful completion, which is wrong for long-running services. Check your liveness probes and ensure your process stays running. Use kubectl describe pod to see the restart reason.

What happens to a pod when its node fails?

If the pod is managed by a Deployment or ReplicaSet, Kubernetes schedules a replacement pod on another healthy node after the node is marked NotReady (typically 5 minutes). Standalone pods are not replaced automatically.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.