StatefulSets: Identity, Ordering and What They Don't Do
Stable names, per-replica volumes and ordered rollouts — plus the honest limits that make operators the better answer for databases.
When Replicas Aren't Interchangeable
A Deployment assumes its pods are identical and disposable. Any replica can serve any request; names are random; order doesn't matter. For a stateless API that's exactly right.
Some workloads need the opposite. A database replica set has a primary and followers. A Kafka broker owns specific partitions. A distributed cache assigns key ranges by node. These need identity — a name that survives rescheduling, so peers can find each other and so the right data lands on the right pod.
That's a StatefulSet.
Three Guarantees
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres-headless
replicas: 3
selector:
matchLabels: { app: postgres }
template:
metadata:
labels: { app: postgres }
spec:
containers:
- name: postgres
image: postgres:17
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi1. Stable names
Pods are numbered from zero and keep their names forever:
postgres-0 postgres-1 postgres-2
Delete postgres-1 and its replacement is also postgres-1 — not a new random suffix. Compare with a Deployment, where every replacement gets a fresh name like api-7d4f8b9c5-x2kfp.
2. Stable network identity
Paired with a headless Service (clusterIP: None, from the Services guide), each pod gets its own DNS record:
postgres-0.postgres-headless.production.svc.cluster.local
postgres-1.postgres-headless.production.svc.cluster.local
So postgres-1 can address postgres-0 directly and reliably. That's how replicas find their primary, and how brokers find peers — cluster membership without a service-discovery system.
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 54323. Per-replica storage
volumeClaimTemplates is the field that matters most. Each replica gets its own PVC, generated from the template:
data-postgres-0 data-postgres-1 data-postgres-2
This is the direct answer to the RWO problem from the previous guide. A Deployment gives every replica the same PVC, which fails with a multi-attach error on RWO storage. A StatefulSet gives each replica a separate volume — and because postgres-1 always keeps its name, it always reattaches to its own data.
Notice that volumeClaimTemplates sits at the StatefulSet level, not inside spec.template. That's deliberate: the pod template describes each pod, while the claim template describes per-pod storage the controller creates and manages independently.
Ordering Guarantees
By default a StatefulSet is strictly sequential:
- Scale up:
postgres-0must be Running and Ready beforepostgres-1is created. - Scale down: highest ordinal first —
postgres-2, thenpostgres-1. - Rolling update: reverse order, one at a time, waiting for Ready between each.
This is what lets a primary initialise before followers try to replicate from it.
Sequencing is also why a StatefulSet can get stuck: if postgres-0 never becomes Ready, nothing else is created. A single failing readiness probe blocks the entire set — and it looks like Kubernetes doing nothing rather than reporting an error.
spec:
podManagementPolicy: Parallel # start/stop all at onceParallel is appropriate when replicas don't depend on each other's startup order but still need stable identity and per-replica storage.
For updates, partition enables staged rollouts:
spec:
updateStrategy:
rollingUpdate:
partition: 2 # only ordinals >= 2 are updatedSet partition: 2 on a 3-replica set and only postgres-2 updates. Verify it, then lower the partition to roll the rest — a canary within the set.
Check yourself
A 3-replica StatefulSet is scaled to 5. `postgres-3` is stuck in Pending because no node has capacity. What happens to `postgres-4`?
PVCs Outlive the StatefulSet
Deliberately, and it catches people out:
- Scale down from 3 to 1 and the PVCs for
postgres-1andpostgres-2are not deleted. - Delete the StatefulSet entirely and its PVCs remain.
The reasoning is sound — the data is usually the point, and automatic deletion on a scale-down typo would be catastrophic. But it means storage accumulates silently, and scaling back up reattaches the old data rather than starting fresh.
kubectl get pvc -l app=postgres # orphaned claims persist hereNewer Kubernetes offers persistentVolumeClaimRetentionPolicy to automate this for whenDeleted and whenScaled, but the default remains retain. Check what your cluster's version supports before relying on it.
Combine this with reclaimPolicy: Delete on the StorageClass and you have a sharp edge: PVCs survive a StatefulSet deletion, but deleting those PVCs destroys the underlying disks immediately. Tidying up orphaned claims is a data-destroying operation. Check what's in them first.
The Honest Limitation
A StatefulSet gives you identity, ordering and per-replica storage. That is all it gives you.
It does not give you:
- Replication — configuring a primary and followers is your job
- Failover — promoting a replica when the primary dies
- Backups and point-in-time recovery
- Version upgrades that respect the database's own procedure
- Connection routing that follows the current primary
Every one of those is essential to running a database, and every one is something you'd be implementing yourself. That's a substantial project, not a configuration task.
This is why the practical answer for databases on Kubernetes is usually an operator — CloudNativePG for PostgreSQL, Strimzi for Kafka, and similar. An operator is a controller that encodes operational knowledge: it watches a custom resource and handles failover, backups and upgrades using the same reconciliation model from phase 1. You declare "a 3-node Postgres cluster with daily backups" and the operator does the work.
A managed database service is also a legitimate answer. "We didn't run the database in Kubernetes" is a perfectly good architectural decision, and worth saying out loud in an interview rather than assuming everything must live in the cluster.
Deployment or StatefulSet?
| Need | Use |
|---|---|
| Stateless app, interchangeable replicas | Deployment |
| Shared storage all replicas write to | Deployment + an RWX volume |
| Per-replica storage, stable names | StatefulSet |
| Peers addressing each other directly | StatefulSet + headless Service |
| A production database | An operator, or a managed service |
Check yourself
What does volumeClaimTemplates provide that a Deployment referencing a PVC cannot?
What's Next
Storage is handled. The next guide covers the resource settings that determine whether your pod gets scheduled at all — and the two limit types that fail in completely different ways.