Crisp answer: Deployments manage stateless pods that are interchangeable. StatefulSets manage stateful pods that each need a stable identity, stable storage, and ordered startup and shutdown.
Deployment — for stateless workloads
A Deployment creates a ReplicaSet which creates pods. All pods are identical and interchangeable. The Deployment can replace any pod with a new one from the same template — the new pod gets a random name and may land on a different node. This is fine for stateless apps: web servers, API services, workers.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
spec:
containers:
- name: api
image: myapp:v1
Pods get random names: api-server-7d4f9b8c6-xk2pq, api-server-7d4f9b8c6-mn3rt
Scale up: new pod starts immediately, no ordering. Scale down: any pod can be deleted, no ordering. Rolling update: new pods start before old pods stop (configurable).
StatefulSet — for stateful workloads
A StatefulSet gives each pod a stable, predictable identity that persists across restarts and rescheduling:
- Stable pod name:
postgres-0,postgres-1,postgres-2— always these exact names, regardless of which node the pod runs on - Stable network identity: Each pod gets a DNS record:
postgres-0.postgres.default.svc.cluster.local - Stable storage: Each pod gets its own PVC created from a
volumeClaimTemplate. The PVC persists when the pod is deleted and is reattached to the replacement pod with the same ordinal. - Ordered startup: Pods start in order: 0 must be Running before 1
starts. This allows
postgres-0to become the primary before replicas join. - Ordered shutdown: Pods terminate in reverse order: N-1, N-2, ... 0
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres # Headless service for DNS
replicas: 3
selector:
matchLabels:
app: postgres
template:
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
This creates: postgres-0 with PVC data-postgres-0, postgres-1 with
PVC data-postgres-1, etc.
The headless service:
StatefulSets use a headless service (clusterIP: None) to create DNS records
for individual pods. Normal Services load-balance across pods. A headless
Service creates A records directly to pod IPs:
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None # headless
selector:
app: postgres
ports:
- port: 5432
# DNS resolution with headless service:
nslookup postgres-0.postgres.default.svc.cluster.local
# → 10.244.1.5 (the actual pod IP, not a ClusterIP VIP)
# Normal service resolution:
nslookup postgres.default.svc.cluster.local
# → 10.96.45.12 (ClusterIP that load-balances across all pods)
When to use each:
| Use case | Type |
|---|---|
| Web API server, microservice | Deployment |
| Worker queue consumer | Deployment |
| Postgres, MySQL, Redis with replication | StatefulSet |
| Kafka, Elasticsearch | StatefulSet |
| Prometheus, Vault | StatefulSet |
| CNPG (CloudNativePG) operator | Manages StatefulSets internally |
Practical differences:
# Delete a Deployment pod — it's immediately replaced by a new random pod:
kubectl delete pod api-server-7d4f9b8c6-xk2pq
# New pod: api-server-7d4f9b8c6-zzq9m (different name, same template)
# Delete a StatefulSet pod — it's replaced with the SAME name and reattaches the SAME PVC:
kubectl delete pod postgres-1
# New pod: postgres-1 (same name, reattaches data-postgres-1 PVC)
# Scale down a StatefulSet — ordinals are preserved:
kubectl scale statefulset postgres --replicas=2
# postgres-2 is deleted. postgres-0 and postgres-1 remain.
# Scale back to 3: postgres-2 is created and gets data-postgres-2 back.
What to say in the interview:
"Deployments manage interchangeable stateless pods — any pod can be killed and replaced with a new one from the same template, names are random. That's fine for API servers and workers. StatefulSets give each pod a stable identity: a predictable name like postgres-0, a DNS record specific to that pod via a headless service, and its own PVC from a volumeClaimTemplate that persists when the pod is deleted and reattaches to the replacement. Startup and shutdown are ordered so a primary can be established before replicas join. I run Postgres, Vault, and Prometheus as StatefulSets in my homelab through CNPG and the respective operators — they all rely on this stable identity for leader election and replication."