Databases need stable, predictable identities and durable storage — requirements that regular Deployments don't fully address. StatefulSets are Kubernetes' purpose-built solution for these stateful workloads.
Why Not Use a Regular Deployment for a Database
Deployments treat all Pod replicas as interchangeable, with random names and no guaranteed storage-to-Pod mapping. Databases need each replica to keep the same identity and the same attached storage across restarts — exactly what StatefulSets provide.
Key StatefulSet Guarantees
- Stable, predictable Pod names (e.g.
postgres-0,postgres-1) - Each Pod gets its own dedicated PersistentVolumeClaim that persists across restarts
- Ordered, sequential deployment and scaling (Pod 0 starts before Pod 1, and so on)
Step 1 — Create a Headless Service
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
clusterIP: None makes this a "headless" service, giving each Pod its own stable DNS entry rather than a single load-balanced IP.
Step 2 — Create the StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
volumeClaimTemplates automatically creates a dedicated PersistentVolumeClaim for each replica — this is the key feature distinguishing StatefulSets from Deployments.
Step 3 — Create the Password Secret First
kubectl create secret generic postgres-secret --from-literal=password=CHANGE_ME_STRONG_PASSWORD
Step 4 — Apply the StatefulSet
kubectl apply -f postgres-statefulset.yaml
Step 5 — Verify It's Running
kubectl get statefulsets
kubectl get pods
kubectl get pvc
Connecting to the Database from Another Pod
postgres-0.postgres.default.svc.cluster.local:5432
Each replica gets a predictable DNS name in the form POD_NAME.SERVICE_NAME.NAMESPACE.svc.cluster.local.
Should You Run Your Production Database in Kubernetes?
Running a single-instance database in Kubernetes for convenience (co-located with your app) is reasonable for smaller projects. For genuinely critical production data with replication/HA requirements, consider whether a dedicated, purpose-built database VPS (see Databases category) or a managed database service might offer more operational simplicity and maturity than self-managing complex database clustering within Kubernetes.
Common Errors
StatefulSet Pod stuck in "Pending" — usually a PVC binding issue; check kubectl describe pvc for the specific reason (often insufficient storage capacity or no matching StorageClass).
Data appears lost after Pod restart — verify you didn't delete the PVC along with the Pod; PVCs from StatefulSets aren't automatically deleted when Pods are removed, by design.
Continue Reading
- How to Manage Persistent Storage in Kubernetes (PersistentVolumes & PVCs)
- Kubernetes ConfigMaps and Secrets: Managing Configuration Safely
- How to Install and Secure MySQL 8 on Ubuntu & Debian
Browse more articles in Kubernetes & Container Orchestration.