StatefulSets + DaemonSets cheatsheet.

StatefulSet

For stateful workloads (DBs, Kafka, etc):

  • Stable network identity (<set>-0, <set>-1, …).
  • Ordered start / stop.
  • Per-replica PVC.
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: pg }
spec:
  serviceName: pg                  # headless service
  replicas: 3
  selector: { matchLabels: { app: pg } }
  template:
    metadata: { labels: { app: pg } }
    spec:
      containers:
        - name: pg
          image: postgres:16
          ports: [{ containerPort: 5432, name: pg }]
          volumeMounts: [{ name: data, mountPath: /var/lib/postgresql/data }]
  volumeClaimTemplates:
    - metadata: { name: data }
      spec:
        accessModes: [ReadWriteOnce]
        resources: { requests: { storage: 10Gi } }
---
apiVersion: v1
kind: Service
metadata: { name: pg }
spec:
  clusterIP: None                  # headless
  selector: { app: pg }
  ports: [{ port: 5432 }]

Pods get:

  • pg-0, pg-1, pg-2 names.
  • DNS: pg-0.pg.default.svc.cluster.local.
  • PVCs: data-pg-0, data-pg-1, data-pg-2.

OrderedReady / Parallel

spec:
  podManagementPolicy: OrderedReady    # default; one at a time
  # or:
  podManagementPolicy: Parallel        # start all together

Update strategies

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0                   # rolling from highest ordinal down
      maxUnavailable: 1

partition: update only pods with ordinal >= partition. Useful for canary in stateful.

PVC retention

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain              # or Delete
    whenScaled: Retain

Avoids losing data on accidental delete.

DaemonSet

One pod per node (or selected nodes):

apiVersion: apps/v1
kind: DaemonSet
metadata: { name: node-exporter, namespace: monitoring }
spec:
  selector: { matchLabels: { app: node-exporter } }
  template:
    metadata: { labels: { app: node-exporter } }
    spec:
      tolerations:
        - operator: Exists           # run on every node
      hostNetwork: true              # for system-level access
      hostPID: true
      containers:
        - name: exporter
          image: prom/node-exporter
          args: ["--path.rootfs=/host"]
          volumeMounts:
            - { name: rootfs, mountPath: /host, readOnly: true }
      volumes:
        - name: rootfs
          hostPath: { path: / }

Use for: monitoring agents, log shippers, CNI, CSI.

nodeSelector / affinity

spec:
  template:
    spec:
      nodeSelector:
        disk: ssd
      
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - { key: node-role.kubernetes.io/worker, operator: Exists }

Job

Run once until completion:

apiVersion: batch/v1
kind: Job
metadata: { name: migrate }
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: migrate
          image: myapp:v1
          command: ["python", "manage.py", "migrate"]
kubectl get jobs
kubectl logs job/migrate
kubectl delete job migrate

Parallel job

spec:
  parallelism: 5
  completions: 10           # need 10 successful runs
  backoffLimit: 3

For batch workloads.

CronJob

apiVersion: batch/v1
kind: CronJob
metadata: { name: backup }
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: myapp:v1
              command: ["./backup.sh"]
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  concurrencyPolicy: Forbid     # don't run if previous still running
  startingDeadlineSeconds: 300

Concurrency: Allow (default), Forbid, Replace.

Trigger manually

kubectl create job --from=cronjob/backup backup-manual

ttlSecondsAfterFinished

spec:
  ttlSecondsAfterFinished: 300     # auto-delete 5min after success

Cleanup completed Jobs.

suspending CronJob

spec:
  suspend: true
kubectl patch cronjob backup -p '{"spec":{"suspend":true}}'

Indexed Job

spec:
  completionMode: Indexed
  parallelism: 5
  completions: 5
  template:
    spec:
      containers:
        - name: worker
          env:
            - name: JOB_INDEX
              valueFrom: { fieldRef: { fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index'] } }

Each pod gets unique index 0..N-1. Useful for partitioned work.

podFailurePolicy

spec:
  podFailurePolicy:
    rules:
      - action: FailJob
        onExitCodes: { containerName: main, values: [42] }
      - action: Ignore
        onExitCodes: { containerName: main, values: [1] }

Fine-grained control over what counts as failure.

Common mistakes

  • StatefulSet without headless service → no DNS per pod.
  • DaemonSet without tolerations → not on tainted nodes (e.g., control plane).
  • Job without ttlSecondsAfterFinished → completed pods accumulate.
  • CronJob Allow concurrency for long jobs → overlapping runs.
  • StatefulSet scale-down leaves PVCs unless retention policy set.

Read this next

If you want my StatefulSet templates (Postgres, Redis), they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .