StatefulSet vs. Deployment: differences and use cases
Compare Kubernetes StatefulSets and Deployments, including identity, storage, scaling, rollout behavior, and common use cases.
Not every replicated workload is stateless. Deployments treat their Pod replicas as interchangeable. StatefulSets give Pods stable identity and storage associations for workloads that care about order, names, and durable state.
The choice affects how a replacement finds its data, how peers discover each other, and how an update proceeds. Both controllers can use persistent storage; the important distinction is whether each replica needs its own identity and storage association.
What is a Deployment in Kubernetes?
A Deployment is a Kubernetes resource object that provides declarative updates for Pods that encapsulate application containers. It manages ReplicaSets to maintain the desired number of replicas and roll out changes to their Pod template. Every Pod has a unique name and UID, but a Deployment does not preserve a particular replica's name when replacing it.
Deployments are typically used to scale interchangeable replicas, perform controlled rollouts for application code, and perform rollbacks when necessary. Automatic scaling requires a separate controller, such as a configured Horizontal Pod Autoscaler; a Deployment does not change its replica count merely because traffic increases.
Kubernetes administrators rely on Deployments to manage a containerized application's lifecycle by defining the number of Pods to be deployed, the image to be used for the application, and how to perform code updates. Kubernetes Deployments help automate repeatable application updates, subsequently reducing the effort, time, and number of errors associated with manual updates.
Components of a Kubernetes Deployment
The Deployment manifest describes the workload. Services and persistent storage are separate resources that can support it, depending on the application's requirements:
- Deployment template: This is a JSON or YAML configuration file that is used to define the
Deployment's configuration specification. The Kubernetes Deployment controller relies on the
desired state described in the manifest to manage ReplicaSets and their Pods. The JSON or
YAML file includes a Pod template that defines what each Pod should look like,
as well as other common parameters, such as:
- Number of Pod replicas
- Container image and its tag or digest
- Secrets, ConfigMaps, and other settings injected into the Pod
- Pod labels that match the Deployment selector and any Service selector
- Service: Provides a stable network endpoint for selected Pods. A Service selects Pod labels; it does not select the Deployment object itself. A Deployment can run without a Service.
- PersistentVolume and PersistentVolumeClaim: A PV represents provisioned storage, and a PVC requests storage that Pods can mount. Storage may be local or network-backed. Persistent storage is optional, and attaching it does not give Deployment replicas stable identities.
Deployment configuration manifest
The example keeps three interchangeable web replicas and one shared PVC. The later StatefulSet example uses the same web server with a separate claim for each replica, making the storage association easier to compare.
Use a development Kubernetes cluster with Linux nodes, kubectl, and permission to create the
workloads, Services, and claims below. The shared Deployment claim requires a StorageClass that
supports ReadWriteMany. The StatefulSet example requires a default StorageClass that can provision
ReadWriteOnce volumes, or an explicit compatible class in its claim template. These capabilities
come from the storage backend; a class named default is not automatically the cluster's default.
Check your context and available classes, then create a namespace for the examples:
kubectl config current-context
kubectl get storageclass
kubectl create namespace workload-demoThe two examples use different Pod labels so their controllers and Services can coexist. Save the
Deployment below as darwin-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: darwin-deployment
namespace: workload-demo
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 1
selector:
matchLabels:
app: darwin-deployment
replicas: 3
template:
metadata:
labels:
app: darwin-deployment
spec:
containers:
- name: web-app
image: nginx:1.30.4-alpine
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /
port: http
periodSeconds: 5
volumeMounts:
- name: darwin-volume
mountPath: /usr/share/nginx/html/shared
readOnly: true
volumes:
- name: darwin-volume
persistentVolumeClaim:
claimName: darwin-volume-claimThis Deployment runs three replicas of the official NGINX image.
Each Pod mounts darwin-volume-claim at the same path. The volume name in volumeMounts matches
the name in volumes; the claim name identifies the separate PVC.
Save the following claim as darwin-volume-claim.yaml. Replace shared-filesystem with the actual
name of your RWX-capable StorageClass:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: darwin-volume-claim
namespace: workload-demo
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: shared-filesystemThe shared directory starts empty and must be populated separately to serve shared files. Mounting
only /usr/share/nginx/html/shared leaves NGINX's built-in welcome page at / available for the
readiness probe. The web Pods mount the directory read-only; RWX support alone would not protect
against unsafe concurrent writes.
Many local clusters provide only RWO storage by default. Such a class cannot satisfy this shared
RWX claim. For a Deployment that does not need shared files, omit the PVC and the
volumes and volumeMounts entries, and skip the commands that create, inspect, or delete
darwin-volume-claim. Our
persistent-volume guide explains the storage
choices in more detail.
A Service provides a stable endpoint for the web Pods. Save this ClusterIP Service as service.yaml:
apiVersion: v1
kind: Service
metadata:
name: darwin-service
namespace: workload-demo
spec:
ports:
- name: http
port: 80
targetPort: http
selector:
app: darwin-deployment
type: ClusterIPThe Service selector matches the Deployment's Pod labels. ClusterIP exposes the endpoint inside the cluster. A NodePort Service can additionally expose a node port, but external access is not required to run the Deployment or compare these controllers.
Apply the claim, Service, and Deployment from the directory containing the files:
kubectl apply -f darwin-volume-claim.yaml
kubectl apply -f service.yaml
kubectl apply -f darwin-deployment.yaml
kubectl --namespace workload-demo rollout status deployment/darwin-deployment --timeout=300sDiscovering Deployment details
Administrators can use kubectl to inspect the Deployment and the Pods it controls. Check the
Deployment's readiness and the shared claim:
kubectl --namespace workload-demo get deployment darwin-deployment
kubectl --namespace workload-demo get pvc darwin-volume-claimTo check the Pods created through its ReplicaSet, run:
kubectl --namespace workload-demo get pods -l app=darwin-deploymentExpect three ready replicas and one bound claim. Deployment Pod names include the Deployment name, a ReplicaSet template hash, and a generated suffix. A replacement gets a new name and UID.
If the rollout stalls, inspect the Pod and PVC events. An unsupported access mode, missing storage class, or image pull failure can prevent Pods from becoming ready:
kubectl --namespace workload-demo describe pods -l app=darwin-deployment
kubectl --namespace workload-demo describe pvc darwin-volume-claimScaling deployments
The kubectl command can also change the desired replica count. To increase darwin-deployment
to five Pods, run:
kubectl --namespace workload-demo scale deployment/darwin-deployment --replicas=5This is manual scaling. Update spec.replicas in the source manifest as well if five is the new
desired state; reapplying a manifest that still specifies three can undo the change. For scaling
based on load, configure an appropriate
Horizontal Pod Autoscaler
and its required metrics.
Kubernetes deployment strategies
The Deployment API has two built-in values for spec.strategy.type:
- Recreate: Terminates the old revision's Pods before creating the new revision during an update. This creates an interruption for a service that depends on those Pods. It is not a general single-writer guarantee for every failure or manual Pod deletion.
- RollingUpdate: Gradually replaces old replicas according to
maxSurgeandmaxUnavailable. The example permits two extra replicas and one unavailable replica during a rollout. Updates are not necessarily one at a time, and zero downtime depends on readiness, capacity, application compatibility, and graceful shutdown.
Ramped is a common name for a gradual rollout, rather than another Deployment API strategy.
Canary deployment keeps old and new versions running while evaluating a limited release. It
usually needs separate Deployments and deliberate traffic routing, or a rollout controller; setting
strategy.type: Canary is not supported by a standard Deployment.
Deployment revisions support rollback, but reverting a Pod template does not undo a database schema change or restore data on a volume. The Deployment documentation describes strategy settings, progress, and revision history.
What is a StatefulSet in Kubernetes?
A StatefulSet is a Kubernetes resource object that manages Pods with stable identities. Each replica
has a numeric ordinal and a name such as darwin-0, and can have its own persistent storage. When a Pod is replaced,
the replacement keeps that name and storage association, but receives a new Kubernetes UID and may
have a different IP address or run on another node.
The controller uses a common Pod template, but the replicas are not interchangeable when the application depends on their identities or individual data. StatefulSets manage Pods directly and record template revisions with ControllerRevisions. They support rollback even though they do not create ReplicaSets.
StatefulSets are typically used for stable network identities, storage associated with individual
replicas, or ordered startup and updates. The default OrderedReady policy creates Pods in ordinal
order and waits for readiness; scaling down proceeds in reverse order. Parallel relaxes ordering
for scaling when the application can handle it. A StatefulSet does not configure application-level
replication or leader election.
Components of a Kubernetes StatefulSet configuration manifest
A Kubernetes StatefulSet configuration comprises the following:
- StatefulSet: Defines the Pod template, selector, desired replica count, and update behavior.
- Headless Service: Supplies the governing network domain for Pod DNS names. It has no virtual Service IP and returns Pod endpoints rather than providing ordinary Service load balancing.
- Volume claim template: Creates a separate PVC for each replica when that replica needs persistent storage. Stable identity can also be useful without a claim template.
StatefulSet configuration manifest
Save the following configuration as statefulset.yaml. It uses the same namespace and NGINX image,
but a separate selector and a claim template named www:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: darwin
namespace: workload-demo
spec:
selector:
matchLabels:
app: darwin-statefulset
serviceName: "darwin-set"
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app: darwin-statefulset
spec:
containers:
- name: darwin-app
image: nginx:1.30.4-alpine
ports:
- containerPort: 80
name: web
readinessProbe:
httpGet:
path: /
port: web
periodSeconds: 5
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html/data
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1GiThe www volume mount refers to the claim template. For the three replicas, Kubernetes creates
www-darwin-0, www-darwin-1, and www-darwin-2. Each Pod mounts its own claim at
/usr/share/nginx/html/data. There is no standalone darwin-claim.yaml to apply in this example.
The template omits storageClassName, so it uses the cluster's default class. If needed, add
storageClassName under volumeClaimTemplates[].spec with a compatible class name. RWO allows
read-write attachment from one node; it does not mean exactly one Pod. ReadWriteOncePod provides
single-Pod access where supported by the CSI driver. Neither access mode makes backups or replicates
the application's data. See the
PersistentVolume documentation
for backend support and access modes.
Save the governing headless Service as darwin-service.yaml. Its name, darwin-set, matches the
StatefulSet's spec.serviceName, and its selector matches the StatefulSet's Pod labels:
apiVersion: v1
kind: Service
metadata:
name: darwin-set
namespace: workload-demo
spec:
ports:
- port: 80
name: web
targetPort: web
clusterIP: None
selector:
app: darwin-statefulsetCreate the headless Service before the StatefulSet, then wait for the Pods:
kubectl apply -f darwin-service.yaml
kubectl apply -f statefulset.yaml
kubectl --namespace workload-demo rollout status statefulset/darwin --timeout=300sWith storage provisioned and readiness passing, these commands create darwin-0, darwin-1, and
darwin-2 in order. Ready Pods have names such as darwin-0.darwin-set.workload-demo.svc in cluster
DNS. A replacement can retain that DNS name without retaining its IP address. Some clustered
applications need discovery before readiness; their bootstrap configuration must account for the
Service's ready-endpoint behavior.
Discovering StatefulSet details
Inspect the named Pods and the three claims:
kubectl --namespace workload-demo get pods -l app=darwin-statefulset
kubectl --namespace workload-demo get pvc www-darwin-0 www-darwin-1 www-darwin-2The Deployment shares one explicitly configured claim, while this StatefulSet creates one claim per
replica. If creation stops at darwin-0, inspect that Pod and www-darwin-0: with OrderedReady, a
pending volume or failing readiness probe prevents the next Pod from starting.
Verify the storage association
For the disposable NGINX example, write a marker into darwin-0's volume and record the Pod UID:
kubectl --namespace workload-demo exec darwin-0 -- \
sh -c 'hostname > /usr/share/nginx/html/data/identity.txt'
kubectl --namespace workload-demo get pod darwin-0 \
-o custom-columns=NAME:.metadata.name,UID:.metadata.uidRestart the StatefulSet through its rolling update mechanism, then read the marker:
kubectl --namespace workload-demo rollout restart statefulset/darwin
kubectl --namespace workload-demo rollout status statefulset/darwin --timeout=300s
kubectl --namespace workload-demo get pod darwin-0 \
-o custom-columns=NAME:.metadata.name,UID:.metadata.uid
kubectl --namespace workload-demo exec darwin-0 -- \
cat /usr/share/nginx/html/data/identity.txtExpect the same Pod name, a new UID, and the saved value darwin-0. The replacement reuses
www-darwin-0; the controller does not copy the file into the other replicas' volumes. This checks
controlled Pod replacement, not recovery from storage loss or a cluster failure.
StatefulSet updates and rollback
The default rolling update replaces Pods from the highest ordinal downward and waits for readiness
before continuing. OnDelete is an alternative when operators need to decide when each Pod adopts
the new template. Choose update behavior to match the application's availability and coordination
requirements.
For a StatefulSet with retained revisions, kubectl rollout history statefulset/darwin lists the
history and kubectl rollout undo statefulset/darwin requests the previous revision; include
--namespace workload-demo for this example. Rollback changes the Pod template, not the stored data.
If an ordered rollout stalls on a Pod that never becomes ready, restoring the working template may not be enough. The documented recovery procedure can also require deleting the broken Pod after reverting the template so the controller recreates it. Follow the StatefulSet rollback guidance and the application's recovery procedure. Avoid force-deleting a stateful Pod when the old process may still be running.
Kubernetes Deployment vs. StatefulSet: how to choose
The table below shows the primary differences between a StatefulSet and a Deployment:
| Aspect | Deployment | StatefulSet |
|---|---|---|
| Data persistence | Can mount persistent storage; replica identity is replaceable. | Can associate persistent storage with a stable replica identity. |
| Pod name and identity | Generated names; a replacement has a new name and UID. | Stable name and ordinal; a replacement still has a new UID and may have a new IP. |
| Interchangeability | Replicas of a revision share a template and are intended to be interchangeable. | Replicas share a template but can have distinct identities and data. |
| Replacement behavior | A ReplicaSet maintains the desired replica count. | The controller recreates the named replica and reuses its retained claims. |
| Volume claims | Pods can reference an existing PVC, as in the shared-volume example; storage is not required. | A claim template creates one PVC per replica per template; storage is not required. |
| Volume access modes | Chosen according to storage capabilities and the application's access pattern. | Also chosen according to storage capabilities and the application's access pattern. |
| Pod networking | An optional Service supplies a stable endpoint for interchangeable replicas. | The governing headless Service supplies per-Pod DNS discovery; a separate client Service can also be used. |
| Scaling order | No stable ordinal or application-specific ordering guarantee. | Ordered creation and reverse-order scale-down by default; Parallel relaxes scale ordering. |
| Updates and rollback | Configurable rolling or recreate updates, with revision-based rollback. | Ordered rolling updates by default, with revision-based rollback and other update options. |
When to use
A StatefulSet is better suited to workloads that require a stable identity or storage association for each replica, such as some databases, brokers, and clustered services. Its replicas are not tied to one Pod per cluster node. A Deployment is suitable for interchangeable application replicas, such as web servers using NGINX or Apache, including applications that keep their durable state elsewhere.
This practical scenario demonstrates how a StatefulSet differs from a Deployment:
Consider a web app that uses a relational database to store data. When traffic to the application increases, administrators intend to scale up the number of Pods to support the workload. A straightforward approach is to change the replica count within the Deployment's configuration manifest; then the Deployment controller will take care of scaling. Since new Pod replicas are assigned the same set of ConfigMaps and environment variables when starting, they communicate with the backend the same way as the original Pod. This works when sessions, database access, and other application state do not depend on a particular web replica. Database connections and backend capacity still need to support the additional traffic.
The relational database may also need more capacity, but adding database Pods requires a database-aware plan. A primary/replica architecture needs bootstrap, replication, leader selection, and client routing configured by the database tooling or an operator. A new replica might copy an initial dataset from a designated source and then follow a transaction log; StatefulSet ordering does not perform those steps or guarantee that the preceding Pod is the correct source.
A StatefulSet supplies stable replica names and, with claim templates, separate storage. When a Pod is replaced, the controller can reuse its retained PVC, provided the storage remains available and can attach where the replacement is scheduled. The database still owns consistency, recovery, and replication. Kubernetes' replicated stateful application tutorial shows how application-specific bootstrap is layered onto the controller. For a simpler database example, see deploying PostgreSQL on Kubernetes.
Use cases
| Requirement | Typical choice | Why |
|---|---|---|
| Web API with sessions and durable state stored externally | Deployment | Replicas can serve requests interchangeably behind a Service. |
| Web replicas reading one shared filesystem | Deployment with a compatible shared PVC | Shared data does not require stable per-replica identities. |
| Database or key-value store with stable member names and separate data directories | StatefulSet, often managed by an operator | Provides identity and storage associations; database tooling supplies replication and failover. |
| Clustered service that depends on ordered member startup | StatefulSet with OrderedReady | The controller can wait for one member's readiness before creating the next. |
| Scaling a replicated service from metrics | A compatible controller with a configured autoscaler | Both the metric policy and application semantics must support replica changes. |
| Training, data processing, or another task that finishes | Job or a specialized workload controller | Completion and retry behavior are different from maintaining a long-running replica count. |
Both Deployments and StatefulSets support controlled updates. Sharing a Pod template, ConfigMaps, or environment variables is also common to both, so those requirements alone do not decide the choice.
Clean up the examples
For orderly shutdown, scale the StatefulSet to zero and wait for its Pods to terminate:
kubectl --namespace workload-demo scale statefulset/darwin --replicas=0
kubectl --namespace workload-demo wait --for=delete pod \
-l app=darwin-statefulset --timeout=180sIf shutdown times out, inspect the remaining Pods before continuing. Once they have stopped, remove the controllers and Services:
kubectl delete -f darwin-deployment.yaml -f service.yaml -f statefulset.yaml -f darwin-service.yamlThe explicitly created Deployment claim and the StatefulSet's claims remain. Claim templates retain PVCs by default; a configured StatefulSet retention policy can change this behavior. Inspect the remaining storage:
kubectl --namespace workload-demo get pvcOnly when the example data is disposable, delete these four claims:
kubectl --namespace workload-demo delete pvc \
darwin-volume-claim www-darwin-0 www-darwin-1 www-darwin-2A PV with reclaim policy Delete can delete its underlying storage when the claim is removed;
Retain requires separate administrator cleanup. If workload-demo contains no other resources you
need, delete the namespace:
kubectl delete namespace workload-demoConclusion
Use Deployments for interchangeable replicated services. Use StatefulSets when stable replica identity, ordered lifecycle behavior, or storage associated with each replica matters. The distinction changes how Kubernetes creates, names, updates, and recovers Pods. Persistent storage alone does not determine the controller, and neither controller supplies database replication or backups.
For Polyaxon users, this matters around supporting services and platform infrastructure. Training jobs and model services have different lifecycles, and the Kubernetes primitive should match the lifecycle instead of fighting it. Polyaxon's job runtime covers tasks such as training and data processing, while its service runtime covers interactive applications and long-running tools. For the platform's metadata database, the PostgreSQL setup guide explains persistence and external database configuration.