DevgainsDevgainsDevgains
All articles

Kubernetes Deployment vs StatefulSet vs DaemonSet: Which to Use

·10 min read·Updated Jul 1, 2026
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, then 1, then 2) 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 reattached

Comparison table

DeploymentStatefulSetDaemonSet
Best forStateless apps, APIs, workersDatabases, brokers, clustered stateful systemsPer-node agents (logs, metrics, networking)
Pod identityRandom, interchangeableStable, ordinal (app-0, app-1)One per node
ScalingAny order, in parallelOrdered (0→1→2, reverse to remove)Follows node count, not replicas
StorageUsually ephemeral / sharedPer-pod PersistentVolume via volumeClaimTemplatesNode-local (hostPath) if any
NetworkingStandard Service (load-balanced)Headless Service for stable per-pod DNSNode-local endpoints
RolloutRolling update + easy rollbackOrdered, one pod at a timeRolling, per node
replicas fieldYesYesNo

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 tolerations on 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 a nodeSelector or node affinity.
  • Deleting a StatefulSet and expecting its data to vanish. By design, PVCs created from volumeClaimTemplates are 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-2 before 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

10 min read

Read next