Kubernetes Deployment vs StatefulSet vs DaemonSet: Which to Use

Cover: illustration generated for Devgains
Choosing between a Kubernetes Deployment vs StatefulSet vs DaemonSet is one of the first real decisions you make when you move a workload onto a cluster, and picking the wrong one shows up later as data corruption, pods that won't schedule, or a rollout that can't roll back. All three are controllers — objects that create and manage pods on your behalf — but each encodes a different assumption about your workload: whether replicas are interchangeable, whether each pod owns durable state, or whether the pod should run on every node. Get the assumption right and the controller does the hard work for you. Get it wrong and you fight the platform.
This guide is a supporting page for the Devgains Kubernetes architecture guide, which explains the control loop these controllers run inside. Here we zoom in on the three workload controllers you'll reach for most and give you a clear rule for each.
Quick answer: Deployment vs StatefulSet vs DaemonSet
- Deployment — for stateless apps where every replica is identical and disposable: web servers, REST/gRPC APIs, stateless workers. Pods get random names and can be created, killed, and replaced in any order.
- StatefulSet — for stateful apps where each pod has a stable identity and its own
persistent storage: databases, message brokers, and clustered systems like Kafka,
PostgreSQL, or Elasticsearch. Pods get stable, ordered names (
db-0,db-1) and keep the same volume across restarts. - DaemonSet — for per-node agents that must run one copy on every node (or every node matching a selector): log shippers, metrics exporters, CNI networking, and node security agents.
The one-line test: identical and disposable → Deployment; unique and durable → StatefulSet; one-per-node → DaemonSet.
Why it matters
The controller you choose determines three things you can't easily change later: how pods are named, how they're ordered during scaling and rollout, and how storage is attached.
A Deployment treats its pods as cattle — it doesn't care which one is which, so it can create
three at once and delete them in any order. That's exactly wrong for a database, where
replica 0 might be the primary and must exist before replica 1 joins. A StatefulSet gives
each pod a stable network identity and a dedicated PersistentVolume that follows it across
reschedules, so a restarted db-0 comes back as db-0 with its data intact. A DaemonSet
ignores replica counts entirely: it targets nodes, adding a pod when a node joins the
cluster and removing it when the node leaves.
Because these behaviors are baked into the controller, the decision is architectural, not cosmetic — which is why it belongs early in your design, alongside how you'll deploy to production.
Architecture: how each controller manages pods
All three sit on top of the same primitive — the pod — and all three run the reconciliation loop described in the architecture guide. The difference is what they reconcile toward.
- Deployment → ReplicaSet → Pods. A Deployment manages a ReplicaSet, which keeps N identical pods running. On an update, the Deployment creates a new ReplicaSet and shifts pods across gradually, giving you rolling updates and instant rollback to the previous ReplicaSet.
- StatefulSet → Pods + PVCs. A StatefulSet creates pods with ordinal names and, via a
volumeClaimTemplate, a matching PersistentVolumeClaim per pod. Pods are created and scaled in order (0, then1, then2) and torn down in reverse, so clustered software can bootstrap safely. It also needs a headless Service to give each pod a stable DNS name. - DaemonSet → one Pod per matching node. The DaemonSet controller watches the node list, not a replica count. It schedules exactly one pod per eligible node and reacts to nodes being added or drained.
Step-by-step: deploy one of each
A stateless API is the textbook Deployment. Three interchangeable replicas, rolled out gradually:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
spec:
containers:
- name: web
image: ghcr.io/acme/web:1a2b3c4
ports: [{ containerPort: 8080 }]A database needs stable identity and its own disk, so it's a StatefulSet. Note the
serviceName (a headless Service) and the volumeClaimTemplates that give each pod a
private volume:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db # headless Service for stable per-pod DNS
replicas: 3
selector:
matchLabels: { app: db }
template:
metadata:
labels: { app: db }
spec:
containers:
- name: postgres
image: postgres:17
ports: [{ containerPort: 5432 }]
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # one PersistentVolume PER pod, kept across restarts
- metadata: { name: data }
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests: { storage: 20Gi }A metrics agent that must run everywhere is a DaemonSet — no replicas field, because the
node count decides how many pods exist:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
spec:
selector:
matchLabels: { app: node-exporter }
template:
metadata:
labels: { app: node-exporter }
spec:
tolerations: # run on control-plane nodes too
- operator: "Exists"
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports: [{ containerPort: 9100 }]Apply and inspect them, and the naming difference is immediately visible:
kubectl apply -f web.yaml -f db.yaml -f node-exporter.yaml
kubectl get pods
# web-6d4c... (random suffix, any order)
# db-0, db-1, db-2 (stable, ordered)
# node-exporter-xxxx (one per node)
# StatefulSet pods keep their PVC across a restart:
kubectl delete pod db-1
kubectl get pod db-1 -w # returns as db-1, same volume reattachedComparison table
| Deployment | StatefulSet | DaemonSet | |
|---|---|---|---|
| Best for | Stateless apps, APIs, workers | Databases, brokers, clustered stateful systems | Per-node agents (logs, metrics, networking) |
| Pod identity | Random, interchangeable | Stable, ordinal (app-0, app-1) | One per node |
| Scaling | Any order, in parallel | Ordered (0→1→2, reverse to remove) | Follows node count, not replicas |
| Storage | Usually ephemeral / shared | Per-pod PersistentVolume via volumeClaimTemplates | Node-local (hostPath) if any |
| Networking | Standard Service (load-balanced) | Headless Service for stable per-pod DNS | Node-local endpoints |
| Rollout | Rolling update + easy rollback | Ordered, one pod at a time | Rolling, per node |
replicas field | Yes | Yes | No |
Best practices
- Default to a Deployment. Most services are stateless. Only reach for a StatefulSet when a pod genuinely needs stable identity and durable per-pod storage — it's more moving parts (a headless Service, PVCs) and slower, ordered rollouts.
- Give StatefulSets a headless Service and a
readinessProbe. Stable DNS (db-0.db.namespace.svc) is how clustered software finds its peers, and ordered rollout waits on readiness — a lying probe breaks the sequence. See liveness vs. readiness probes. - Set resource requests and limits on all three. The scheduler places pods by requests; DaemonSet pods in particular compete for room on every node, so size them conservatively.
- Use
tolerationson DaemonSets when the agent must also run on tainted nodes (e.g. control-plane nodes) — otherwise it silently skips them. - Prefer managed data services when you can. Running PostgreSQL as a StatefulSet is legitimate, but a managed database removes the hardest failure modes; the same connection-pooling concerns apply either way.
Common mistakes
- Running a database as a Deployment. Two replicas of a Deployment share nothing and write over each other's data or fight for the same volume. Stateful software needs a StatefulSet.
- Expecting a DaemonSet to honor
replicas. It has no such field — the node count is the replica count. To limit it to some nodes, use anodeSelectoror node affinity. - Deleting a StatefulSet and expecting its data to vanish. By design, PVCs created from
volumeClaimTemplatesare retained when you delete the StatefulSet, so orphaned volumes (and cloud disk bills) pile up. Clean up PVCs deliberately. - Scaling a StatefulSet down carelessly. Removing
db-2before draining it can drop the newest member of a cluster mid-operation. Scale down in reverse ordinal order and let the app rebalance. - Forgetting the headless Service. Without it, StatefulSet pods have no stable DNS and peers can't find each other.
Takeaways
- Deployment = stateless and interchangeable. Random pod names, parallel scaling, rolling updates with instant rollback. The right default for most services.
- StatefulSet = stable identity + durable storage. Ordinal names, ordered rollout, a PersistentVolume per pod, and a headless Service. For databases and clustered stateful systems.
- DaemonSet = one pod per node. No
replicas; the node set drives the pod count. For logging, metrics, networking, and node-level agents. - The decision is architectural. Naming, ordering, and storage are baked into the controller and hard to change later — pick deliberately, up front.
Keep building your mental model with the Kubernetes cluster and the related DevOps guides: how to roll out new versions with zero downtime and how the control plane turns your YAML into running pods.
FAQ
What is the difference between a Deployment and a StatefulSet in Kubernetes? A Deployment
manages interchangeable, stateless pods with random names that can be created and destroyed in
any order — ideal for web apps and APIs. A StatefulSet gives each pod a stable, ordered
identity (app-0, app-1) and its own persistent volume that survives restarts, which is
what databases and clustered systems need.
When should I use a DaemonSet? Use a DaemonSet when a pod must run on every node (or
every node matching a selector) — node monitoring agents, log collectors, CNI networking
plugins, and security daemons. Unlike a Deployment, it has no replicas field; the controller
runs exactly one pod per eligible node and adds or removes pods as nodes join or leave.
Can I run a database with a Deployment? You shouldn't. A Deployment's pods are interchangeable and share no stable storage, so two replicas will corrupt data or clash over a volume. Databases need the stable identity and per-pod PersistentVolume that a StatefulSet provides.
Do StatefulSet volumes get deleted when I delete the StatefulSet? No. PersistentVolumeClaims
created from volumeClaimTemplates are retained by default so your data isn't lost by accident.
You must delete the PVCs yourself to reclaim the storage.
Deployment vs StatefulSet vs DaemonSet — which is the default choice? Start with a Deployment. Most workloads are stateless, and Deployments give you the simplest scaling and the fastest rollbacks. Move to a StatefulSet only when you need stable identity and durable storage, and to a DaemonSet only when you need one pod per node.
Conclusion
Kubernetes gives you three workload controllers because real systems have three shapes: interchangeable replicas, uniquely-identified stateful members, and per-node agents. A Deployment is the disposable-replica default; a StatefulSet trades speed for stable identity and durable storage; a DaemonSet blankets every node. Match the controller to the shape of your workload and the platform's self-healing, ordered scaling, and safe rollouts all work with you. From here, revisit the architecture guide to see how each controller's reconciliation loop turns these specs into running pods.
References
- Kubernetes: Deployments — rolling updates, rollbacks, and how a Deployment manages ReplicaSets.
- Kubernetes: StatefulSets — stable identity, ordered deployment, and per-pod storage.
- Kubernetes: DaemonSet — running a pod on every node and targeting node subsets.
- Kubernetes: Persistent Volumes — how
volumeClaimTemplatesand PVC retention work. - Kubernetes: Headless Services — stable per-pod DNS for StatefulSets.

