Kubernetes StatefulSets for stateful workloads

Ryan Nakamura Feb 2026
1 tab
# Headless Service for stable DNS
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: production
  labels:
    app: postgres
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

---
# StatefulSet for PostgreSQL
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: production
spec:
  serviceName: postgres
  replicas: 3
  podManagementPolicy: OrderedReady
  selector:
    matchLabels:
      app: postgres
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0
  template:
    metadata:
      labels:
        app: postgres
    spec:
      terminationGracePeriodSeconds: 120
      securityContext:
        runAsUser: 999
        fsGroup: 999
      initContainers:
        - name: init-permissions
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              chown -R 999:999 /var/lib/postgresql/data
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
              name: postgres
          env:
            - name: POSTGRES_DB
              value: myapp
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: postgres-credentials
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-credentials
                  key: password
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
            # Replication config for replicas
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: "2"
              memory: 4Gi
          readinessProbe:
            exec:
              command:
                - pg_isready
                - -U
                - postgres
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            exec:
              command:
                - pg_isready
                - -U
                - postgres
            initialDelaySeconds: 30
            periodSeconds: 30
            timeoutSeconds: 5
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
            - name: postgres-config
              mountPath: /etc/postgresql/conf.d
      volumes:
        - name: postgres-config
          configMap:
            name: postgres-config
  volumeClaimTemplates:
    - metadata:
        name: postgres-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: gp3
        resources:
          requests:
            storage: 50Gi

---
# ConfigMap for PostgreSQL tuning
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-config
  namespace: production
data:
  custom.conf: |
    max_connections = 200
    shared_buffers = 1GB
    effective_cache_size = 3GB
    work_mem = 16MB
    maintenance_work_mem = 256MB
    wal_buffers = 16MB
    checkpoint_completion_target = 0.9
    random_page_cost = 1.1
    log_min_duration_statement = 1000
    log_statement = 'ddl'

---
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  encrypted: "true"
  iops: "3000"
  throughput: "125"
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
1 file · yaml Explain with highlit

StatefulSets manage stateful applications requiring stable identities and persistent storage. Unlike Deployments, StatefulSets provide ordered Pod creation (pod-0, pod-1, pod-2) and stable network identifiers. Each Pod gets a predictable hostname via a headless Service. volumeClaimTemplates create a unique PersistentVolumeClaim for each replica. Pods maintain their identity across rescheduling—pod-0 always reattaches to the same volume. Ordered deployment and scaling ensure pod-0 starts before pod-1. podManagementPolicy: Parallel allows simultaneous startup when ordering is unnecessary. StatefulSets are essential for databases, message queues, and distributed systems like Kafka, Elasticsearch, and ZooKeeper. Update strategies support RollingUpdate with partition for canary rollouts.