A Pod is the smallest thing we can deploy in Kubernetes. It wraps one or more containers that share the same network and storage. Most of the time, it’s just one container per Pod. Think of a Pod as a thin wrapper around our container that gives Kubernetes something to manage.
Pod Lifecycle
Every Pod goes through these phases:
- Pending — accepted by the cluster, but containers aren’t running yet (maybe pulling images or waiting for scheduling)
- Running — at least one container is running
- Succeeded — all containers finished successfully (exit code 0)
- Failed — all containers terminated, at least one failed
- Unknown — can’t get the Pod’s status (usually a node communication issue)
# A basic Pod definition — we rarely write these directly
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: nginx:1.27
ports:
- containerPort: 80
Multi-Container Patterns
Sometimes we need more than one container in a Pod. The containers share localhost and can share volumes.
Sidecar — a helper container that runs alongside our main app. Common examples: log collector, service mesh proxy (Envoy), or a config reloader.
Init Container — runs before the main container starts. Useful for setup tasks like waiting for a database to be ready or running migrations.
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']
containers:
- name: app
image: my-app:latest
Deployments — The Go-To Workload
We almost never create Pods directly. Instead, we use a Deployment, which manages Pods for us. It gives us:
- Desired state — “I want 3 replicas running at all times”
- Rolling updates — updates Pods one by one so there’s zero downtime
- Rollback — if something goes wrong, roll back to the previous version
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3 # keep 3 Pods running
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:v2
ports:
- containerPort: 8080
A Deployment creates a ReplicaSet under the hood, which is the thing that actually ensures the right number of Pods are running. We don’t manage ReplicaSets directly — the Deployment handles it.
# Common Deployment commands
kubectl rollout status deployment/my-app # watch the rollout
kubectl rollout history deployment/my-app # see revision history
kubectl rollout undo deployment/my-app # rollback to previous version
StatefulSets — When Identity Matters
Deployments treat all Pods as interchangeable. But some workloads need stable identity — databases, message queues, etc. StatefulSets give each Pod:
- A stable hostname (
my-db-0,my-db-1,my-db-2) - Persistent storage that sticks with the Pod even if it restarts
- Ordered startup/shutdown (Pod 0 starts first, then Pod 1, etc.)
We use these for things like PostgreSQL clusters, Kafka brokers, or Redis Sentinel.
DaemonSets — One Pod Per Node
A DaemonSet ensures that every node (or a subset) runs exactly one copy of a Pod. When a new node joins the cluster, the DaemonSet automatically schedules a Pod on it. Perfect for:
- Log collectors (Fluentd, Filebeat)
- Monitoring agents (Prometheus Node Exporter)
- Network plugins (Calico, Cilium)
Jobs and CronJobs
Job — runs a Pod to completion and then stops. Great for batch tasks like data processing or database migrations.
CronJob — a Job on a schedule, just like a Linux cron. Runs at specified times.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-backup
spec:
schedule: "0 2 * * *" # every day at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup-tool:latest
restartPolicy: OnFailure
In simple language, Pods are the atomic unit, but we almost always manage them through higher-level workloads. Deployments for stateless apps, StatefulSets for databases, DaemonSets for per-node agents, and Jobs for one-off tasks.