Kubernetes workloads are the controllers that create and manage Pods for you. You rarely create a Pod by hand. Instead you describe the kind of process you have, and the matching controller keeps the right Pods running. Choosing the right one mostly comes down to one question: how should the copies of this process behave?
- Interchangeable copies that should always be running: Deployment.
- Copies that each need a stable name and their own disk: StatefulSet.
- One copy on every node: DaemonSet.
- A task that runs to completion: Job, or CronJob if it runs on a schedule.
This guide explains each one with a working YAML example and the mistakes that come up most often.
Kubernetes workloads at a glance
| Workload | Use it for | Pod names | Storage | Typical examples |
|---|---|---|---|---|
| Deployment | Stateless, long-running services | Random (web-7c9f-x2kq8) | Shared or none | Web servers, APIs, workers |
| StatefulSet | Stateful, clustered software | Stable (db-0, db-1) | One PVC per Pod | Databases, Kafka, etcd |
| DaemonSet | One agent per node | One per node | Usually host paths | Log collectors, monitoring agents, CNI |
| Job | Finite batch work | Random | Optional | Migrations, imports, backfills |
| CronJob | Scheduled batch work | Random | Optional | Nightly reports, cleanup, backups |
Pods: the unit every workload manages
A Pod is the smallest thing Kubernetes schedules. It wraps one or more containers that share a network namespace (they reach each other on localhost) and can share volumes. Pods are disposable. When one dies it isn't repaired, it's replaced by a new Pod with a new name and a new IP.
That is why you shouldn't run bare Pods in production. If the node running a bare Pod fails, nothing recreates it. Workload controllers exist to watch Pods and replace them.
ReplicaSet: keeps N copies running
A ReplicaSet has a label selector, a Pod template and a replica count. Its controller keeps exactly that many matching Pods alive: if one disappears it creates another, and if there are too many it deletes some.
You almost never write a ReplicaSet yourself. A Deployment creates and manages them, one per version of your Pod template. The old ReplicaSets that you see in kubectl get rs, scaled to zero, are how rollback works.
Deployment: the default for stateless apps
A Deployment manages ReplicaSets and adds rolling updates and rollback on top. Use it for anything where every Pod is identical and can be replaced by any other: web servers, APIs, queue consumers.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: ghcr.io/example/web:1.8.2
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256MiDay-to-day operations:
kubectl set image deployment/web web=ghcr.io/example/web:1.9.0
kubectl rollout status deployment/web
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl scale deployment/web --replicas=5A few details worth knowing:
- The
selectoris immutable inapps/v1. Choose labels you won't want to change. RollingUpdatereplaces Pods gradually.Recreatestops all old Pods before starting new ones, which suits apps that can't run two versions side by side, at the cost of downtime.- Zero-downtime rollouts also depend on readiness probes and graceful shutdown. The article on Kubernetes rolling updates without downtime covers that in detail.
StatefulSet: stable identity and storage
Some software needs each replica to be distinguishable. A database needs to know which member is the primary. A Kafka broker needs a fixed ID and must reattach to its own log directory after a restart. A StatefulSet gives each Pod three guarantees:
- A stable name based on an ordinal:
db-0,db-1,db-2. Whendb-1is rescheduled it comes back asdb-1. - A stable DNS name through a headless Service, such as
db-1.db.default.svc.cluster.local. - Its own PersistentVolumeClaim, created from
volumeClaimTemplates.db-1always reattaches todata-db-1.
Pods are also created in order by default (db-0 must be Ready before db-1 starts) and removed in reverse order.
apiVersion: v1
kind: Service
metadata:
name: db
spec:
clusterIP: None # headless: DNS returns Pod IPs
selector:
app: db
ports:
- name: postgres
port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
envFrom:
- secretRef:
name: db-credentials # contains POSTGRES_PASSWORD
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- name: postgres
containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20GiBe clear about what this does not do. The manifest above runs three independent PostgreSQL servers. A StatefulSet provides identity and storage, but replication, leader election and failover are the application's job, or an operator's. In practice most teams run databases through an operator or a managed service. Running databases in production discusses those trade-offs.
Other StatefulSet behaviors:
- PVCs outlive Pods. Scaling from 3 to 2 deletes
db-2but keepsdata-db-2, so scaling back up reattaches the old data. You can change this withpersistentVolumeClaimRetentionPolicy. - Updates roll in reverse ordinal order, from the highest ordinal down. Set
updateStrategy.rollingUpdate.partition: 2to update onlydb-2first, as a canary. podManagementPolicy: Parallelskips the ordering when your software doesn't need it, which makes scaling faster.
The headless Service and per-Pod DNS are explained further in Kubernetes Service types.
DaemonSet: one Pod per node
A DaemonSet runs one copy of a Pod on every node, or on every node that matches a selector. When a node joins the cluster, the DaemonSet schedules a Pod onto it. When a node leaves, the Pod goes with it. Node-level infrastructure runs this way: log collectors, monitoring agents such as node-exporter or an OpenTelemetry Collector agent, CNI plugins, CSI node drivers, and kube-proxy itself.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
namespace: logging
spec:
selector:
matchLabels:
app: log-agent
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app: log-agent
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/logThe toleration lets the agent run on control plane nodes too, which carry a NoSchedule taint. To target a subset of nodes, such as only GPU nodes, add a nodeSelector or node affinity to the Pod template.
A common mistake is to run a Deployment with replicas set to the number of nodes. Nothing guarantees one Pod per node: two replicas can land on the same node, and the count doesn't follow the cluster as nodes are added or removed. If the requirement is "on every node", use a DaemonSet.
Job: run to completion
A Job runs Pods until a given number finish successfully. Use it for database migrations, data imports and one-off backfills.
apiVersion: batch/v1
kind: Job
metadata:
name: migrate-2026-09
spec:
backoffLimit: 3 # retries before the Job is marked failed
activeDeadlineSeconds: 600 # hard time limit for the whole Job
ttlSecondsAfterFinished: 3600 # clean up an hour after it finishes
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: ghcr.io/example/web:1.9.0
command: ["./migrate", "up"]restartPolicy must be Never or OnFailure, never Always. For parallel work, set completions (how many successful Pods you need) and parallelism (how many run at once). Retries mean a task can run more than once, so make it idempotent.
CronJob: Jobs on a schedule
A CronJob creates a Job on a cron schedule, like cron on a Linux host.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
timeZone: "Asia/Bangkok"
concurrencyPolicy: Forbid # skip a run if the last one is still going
startingDeadlineSeconds: 300
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: report
image: ghcr.io/example/reports:2.3.0
args: ["--date", "yesterday"]Without timeZone, the schedule follows the time zone of the kube-controller-manager, usually UTC. concurrencyPolicy can be Allow (the default), Forbid or Replace. The CronJob controller can occasionally miss a run or start two, so scheduled jobs should be idempotent as well.
Choosing the right Kubernetes workload
Work through these in order:
- Does the process finish? Use a Job, or a CronJob if it repeats on a schedule.
- Must it run on every node, or on every node of a certain kind? Use a DaemonSet.
- Does each replica need a stable identity or its own persistent disk? Use a StatefulSet.
- Otherwise, use a Deployment.
Most applications end up as Deployments. If you're reaching for a StatefulSet for your own service, check whether the state could live in a database or object storage instead. Stateless services are much easier to scale and roll out. For the storage side of StatefulSets, see Kubernetes persistent volumes, PVCs and StorageClasses.
FAQ
What is the difference between a Deployment and a StatefulSet?
Deployment Pods are interchangeable, with random names and shared or no storage. StatefulSet Pods have stable names, stable DNS entries and a dedicated PVC each, and they are created and updated in a defined order.
Should I create ReplicaSets directly?
Almost never. Create a Deployment and let it manage ReplicaSets. That way you get rolling updates and rollback for free.
Can a Deployment use a PersistentVolume?
Yes, but every replica mounts the same claim. With a ReadWriteOnce volume, that only works while all replicas land on the same node. Use a ReadWriteMany volume, one replica, or a StatefulSet.
How do I run a Pod on every node in Kubernetes?
Use a DaemonSet. Add tolerations if it also needs to run on tainted nodes such as the control plane, and a node selector to limit it to a subset of nodes.
What happens to StatefulSet volumes when I scale down?
By default the PVCs and their data are kept, and scaling back up reattaches them. Set persistentVolumeClaimRetentionPolicy if you want them deleted.
Workload checklist
- No bare Pods in production: every Pod belongs to a controller.
- Stateless services run as Deployments with readiness probes and resource requests.
- Databases and brokers use a StatefulSet, and replication is handled by the software or an operator.
- Node agents run as DaemonSets with the tolerations they need.
- Jobs and CronJobs are idempotent, with
backoffLimit, a deadline andttlSecondsAfterFinishedset.
To practice these workloads on a live cluster, Vectorkub's free DevOps courses cover them hands-on.
