Workloads¶
Most people's first Kubernetes manifest is a Deployment, and it "just works" — until the day the job at hand isn't stateless. A database that needs to remember which replica is the primary, a log shipper that must run on every node, a migration script that should run once and stop: none of those fit a Deployment, and reaching for one anyway is how a working cluster grows workloads that quietly fight their own controller. The object you pick isn't a style preference — it's a contract about how Kubernetes keeps that workload running when something goes wrong.
Scope: the workload objects — the things that actually keep your containers running. The core concepts page covers the control plane and how to debug a broken Pod; this page goes one layer deeper on which object to create for a given job.
TL;DR — in 30 seconds:
- Deployment is the default for stateless apps — it owns a ReplicaSet that keeps N Pod replicas running; changing the Pod template creates a new ReplicaSet rather than editing the old one in place, which is what makes rollback cheap.
- Reach for StatefulSet the moment a replica's identity or its own storage matters (databases, queues), DaemonSet for "one Pod per node," and Job/CronJob for work that has an end.
- You almost never create a bare Pod directly — if its node dies, nothing replaces it; a controller (ReplicaSet/Deployment/etc.) is what gives you self-healing.
1. The Pod — the atomic unit¶
A Pod is the smallest thing Kubernetes schedules: one or more containers that always land on the same
node, share a network namespace (so they can talk over localhost), and share storage volumes. You
almost never create a bare Pod directly in a real system — if its node dies, it is gone for good, with
nothing to replace it. Instead you create a controller that owns Pods for you and keeps the right
number running.
2. ReplicaSet and Deployment¶
A ReplicaSet does one job: keep exactly N copies of a Pod template running. If a Pod dies or its node disappears, the ReplicaSet's control loop notices the gap between desired and actual count and creates a replacement.
You almost never write a ReplicaSet by hand either — a Deployment wraps it and adds the part teams actually need: rollouts and rollback. A Deployment owns a ReplicaSet the way a ReplicaSet owns Pods:
flowchart LR
dep["Deployment<br/>desired state + rollout history"] --> rs["ReplicaSet<br/>current template, N replicas"]
rs --> p1["Pod"]
rs --> p2["Pod"]
rs --> p3["Pod"]
When you change a Deployment's Pod template (a new image tag, say), it does not edit the existing ReplicaSet in place. It creates a new ReplicaSet with the new template, then shifts replicas from the old one to the new one — a rolling update — while keeping the old ReplicaSet around (scaled to zero) as rollout history. That history is what makes rollback cheap: rolling back a Deployment means shifting replicas back to a previous ReplicaSet's template instead of guessing what the old config was. (Go deeper: the upstream Deployments concept guide — optional, not required to defend this.)
Note. A stuck rollout (new Pods stay
CrashLoopBackOffor never go Ready) is a signal to check the rollout's status and, if the new version is bad, roll back rather than wait it out.
3. Choosing a workload type¶
Deployment is the default for stateless apps, but it is not the only controller. Pick by what the workload actually needs:
flowchart TD
Q1{"Does the work run forever,<br/>or does it complete and stop?"}
Q1 -->|"completes and stops"| Q2{"Once, or on a<br/>repeating schedule?"}
Q1 -->|"runs forever"| Q3{"One Pod per node,<br/>or N replicas somewhere<br/>in the cluster?"}
Q2 -->|"once"| Job["Job"]
Q2 -->|"repeating schedule"| CronJob["CronJob"]
Q3 -->|"one per node"| DaemonSet["DaemonSet"]
Q3 -->|"N replicas somewhere"| Q4{"Does a replica's identity<br/>or its own storage matter?"}
Q4 -->|"yes"| StatefulSet["StatefulSet"]
Q4 -->|"no"| Deployment["Deployment"]
| Object | Keeps running | Identity across restarts | Typical use |
|---|---|---|---|
| Deployment | N replicas of a stateless Pod | None — any replica is interchangeable | web apps, APIs, workers |
| StatefulSet | N replicas, but each with a stable name and storage | Each replica keeps its own name (app-0, app-1, …) and its own PersistentVolume across restarts |
databases, queues, anything where replica identity or its data matters |
| DaemonSet | Exactly one Pod per (matching) node | Tied to the node it runs on | log shippers, node monitoring agents, network/CNI components |
| Job | A Pod (or several) until it completes successfully, not forever | N/A — runs to completion, then stops | a one-off batch task, a migration script |
| CronJob | A Job, created on a schedule | N/A | a nightly report, a periodic cleanup task |
4. StatefulSet, DaemonSet, and Job/CronJob in a bit more depth¶
- StatefulSet — use it the moment a replica's identity matters: a database that needs to know which replica is the primary, or any app where "replica 0's disk" must always mean the same disk. Deployment Pods are cattle (interchangeable); StatefulSet Pods are closer to pets with stable names, stable network identity, and their own storage that follows them across a restart.
- DaemonSet — use it when you need "this workload, on every node" rather than "N copies somewhere in the cluster": a log collector, a metrics agent, or a per-node networking component. Adding a node automatically schedules the DaemonSet's Pod there; no scaling logic needed.
- Job — use it for work that has an end: run to completion, then stop, rather than being kept alive forever like a Deployment's Pods. A Job that fails can be configured to retry.
- CronJob — a Job on a recurring schedule (cron syntax). Think of it as "create this Job at these times" — everything you know about Job applies to each run it creates.
5. How this connects¶
- The debugging loop from the core-concepts page (
get→describe→logs→events) applies to a Pod no matter which controller created it. - Rollout/rollback is what makes a Deployment safe to ship frequently — pair it with the liveness/ readiness probes covered on the core-concepts page so a bad rollout is caught before it takes traffic.
Common gotchas
- Creating a bare Pod for a "real" workload. Fix: a bare Pod has nothing watching it — if its node dies, it's gone for good. Wrap it in a Deployment (or the right controller for the job) so something notices and replaces it.
- Reaching for a Deployment when a replica's identity or its own storage matters. Fix: Deployment Pods are interchangeable cattle with no stable name or dedicated storage — use StatefulSet the moment "replica 0's disk" needs to always mean the same disk.
- Expecting a Deployment update to edit the running ReplicaSet in place. Fix: it creates a new ReplicaSet and shifts replicas over (a rolling update), keeping the old one scaled to zero — that's what makes rollback possible; don't go hunting for an in-place edit that didn't happen.
- Using a Job for work that should run continuously, or a Deployment for a one-off task. Fix: match the "keeps running" column to what the workload needs — a Job runs to completion and stops; a Deployment expects to run forever and treats a completed container as a failure to restart.
Check yourself — you update a Deployment's image. Does it edit the existing ReplicaSet, or create a new one?
It creates a new ReplicaSet with the new Pod template, then shifts replicas from the old ReplicaSet to the new one (a rolling update) — the old ReplicaSet stays around scaled to zero as rollout history, which is what makes rollback cheap.
Check yourself — a workload needs to run on every node in the cluster, not N copies somewhere. Which object, and why not just set a high replica count on a Deployment?
DaemonSet — a Deployment's replica count has no relationship to node count and doesn't automatically track nodes being added or removed. DaemonSet guarantees exactly one Pod per matching node, including new nodes as they join, with no scaling logic needed.
You can defend this when you can say which controller you'd reach for given a workload's shape (stateless vs. needs-identity vs. per-node vs. runs-to-completion), and explain what actually changes inside a Deployment when you update its image.