Deployment vs StatefulSet vs DaemonSet: Choosing the Right Kubernetes Controller
Choose by Invariant, Not by Application Label
The useful question is not whether an application is a database or an agent. Ask what must remain true when a Pod is deleted, rescheduled, or upgraded.
| Controller | Replica identity | Typical placement | Persistent identity/storage | Common use |
|---|---|---|---|---|
| Deployment | Interchangeable | Any eligible nodes | No stable Pod identity | APIs, web apps, stateless workers |
| StatefulSet | Ordered, stable ordinals | Any eligible nodes | Stable DNS and per-Pod claims | Databases, brokers, clustered systems |
| DaemonSet | Usually one per eligible node | Node coverage | Normally node-local | Log, network, security, storage agents |
All three manage Pods and replace failures. Their guarantees differ.
---
Deployment: Replaceable Replicas
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: catalog
template:
metadata:
labels:
app: catalog
spec:
containers:
- name: api
image: registry.example.com/catalog:2.7.1
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
resources:
requests:
cpu: 200m
memory: 256Mi
A Deployment owns ReplicaSets, and a ReplicaSet maintains the replica count. Pod names and IPs change. Clients should use a Service, and application state should live outside the Pod or on storage designed for shared access.
maxUnavailable: 0 and maxSurge: 1 preserve four Ready replicas during a normal rollout but require room for a fifth Pod. A restrictive quota or a full cluster can stall that rollout.
---
StatefulSet: Stable Network and Storage Identity
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: redis-headless
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.4
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
The Pods are redis-0, redis-1, and redis-2. If redis-1 is recreated, it keeps the same ordinal and reattaches its own data-redis-1 claim. A headless Service gives stable DNS such as redis-1.redis-headless.default.svc.cluster.local.
That stability does not make the application highly available. Kubernetes does not understand leader election, replication lag, quorum, or safe promotion unless an operator or the application handles them. StatefulSet also does not delete claims by default merely because a Pod or StatefulSet is deleted - a valuable data-safety behavior that requires an explicit cleanup plan.
Ordered rollout can stop at a broken ordinal. Inspect each member and the application cluster state before forcing deletion or changing update strategy.
---
DaemonSet: Node Coverage
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
namespace: observability
spec:
selector:
matchLabels:
app: log-agent
template:
metadata:
labels:
app: log-agent
spec:
tolerations:
- operator: Exists
containers:
- name: agent
image: registry.example.com/log-agent:1.9.0
resources:
requests:
cpu: 50m
memory: 64Mi
A DaemonSet creates Pods on matching nodes as nodes appear and removes them as nodes disappear. Typical examples include CNI components, log collectors, node metrics exporters, and security agents.
Do not set replicas; desired count follows eligible nodes. Tolerating every taint may be correct for infrastructure agents, but it also places the Pod on control-plane or specialized nodes. Combine tolerations with node affinity when coverage should be narrower.
---
Common Wrong Turns
- A Deployment with one ReadWriteOnce volume is not automatically a safe database. A replacement may attach the disk, but stable membership and update ordering are absent.
- A StatefulSet is unnecessary merely because an API writes data to an external database. Its Pods remain interchangeable, so a Deployment fits.
- A DaemonSet is not a way to run a fixed number of workers. Cluster growth changes its count.
- A bare Pod offers no controller-driven replacement or rollout and is rarely appropriate for an application.
- A Job or CronJob is the right abstraction for work that must finish, rather than a Deployment whose process exits and restarts forever.
Use kubectl rollout status and kubectl describe for all controller rollouts, but evaluate success using the workload's own invariants. Practice stable identity in StatefulSet Redis Identity, node disruption behavior in Taints and PDB Maintenance, and one-shot semantics in Recover a Failed Job.