I Deleted the Same Pod Twice. Only One of Them Remembered.
There is nothing quite like the cold sweat of watching a production Kubernetes cluster swallow your stateful workload because you trusted a standard deployment manifest. Last Tuesday, I deleted two seemingly identical database pods in a staging cluster to test failover behavior, fully expecting them both to spin back up with their underlying data intact and waiting. One pod came back up, reattached its volume, and resumed serving traffic like nothing had happened. The other pod vanished into the ether, leaving behind an empty directory, a frantic log stream, and a very unhappy PostgreSQL instance wondering where its tables went.
That moment of panic is a rite of passage for every infrastructure engineer. We spend years learning how to write declarative configurations, manage rolling updates, and tune resource limits, only to get blindsided by the subtle nuances of Kubernetes storage persistence. If you treat your stateful workloads like stateless cattle, Kubernetes will happily slaughter your data the moment a node restarts or a pod is rescheduled. Today, we are going to dive deep into why this happens, how Kubernetes actually handles state under the hood, and what you need to do to ensure your data survives a pod deletion event.
The Problem Everyone Ignores
When we first start working with Kubernetes, we fall in love with its ephemeral nature. Deployments, ReplicaSets, and pods are designed to be completely disposable, spun up and torn down at a moment's notice to handle scaling events or node failures. This abstraction is wonderful for stateless microservices, web servers, and API gateways where local data persistence does not matter. The problem arises when developers apply this exact same mental model to databases, message queues, and caching layers without accounting for storage lifecycles.
Above: High-level architecture overview of the topic covered in this article.
If you attach a standard emptyDir volume or rely on ephemeral local storage, your container data lives and dies with the pod itself. The moment that pod is deleted, rescheduled to another node, or evicted due to memory pressure, the underlying storage is wiped clean. Even worse, many teams configure PersistentVolumeClaims incorrectly by using dynamic provisioning with default Delete reclamation policies tied to standard deployments. When the deployment scales down or gets recreated, the PVC gets orphaned or wiped out because a standard Deployment does not guarantee stable network identifiers or persistent storage binding across pod rescheduling events.
We ignore this underlying complexity because local testing on Minikube or Docker Desktop hides the harsh reality of distributed storage. On your local machine, host path mounts persist across container restarts, masking the architectural flaws in your Kubernetes manifests. When you push that exact same configuration to a multi-node production cluster in AWS or GCP, the cloud provider does not care about your local assumptions. A node goes down, Kubernetes reschedules the pod on a completely different physical machine, and your local volume is left behind on the old node. The result is silent data corruption, broken application state, and emergency incident responses that ruin your weekend.
What Actually Works
To solve this problem permanently, we need to abandon standard Deployments for stateful workloads and embrace StatefulSets paired with robust PersistentVolumeClaims. Unlike standard deployments, StatefulSets guarantee the ordering and uniqueness of pods, assigning them persistent network identifiers and stable storage mapping. When a pod in a StatefulSet is deleted or rescheduled, Kubernetes ensures that the new pod inherits the exact same persistent volume claim, reattaching your cloud provider's block storage seamlessly.
Before we look at the implementation, let us examine the mechanics of how storage binding works in a production cluster. When you define a PersistentVolumeClaim template inside a StatefulSet, Kubernetes dynamically provisions a unique PersistentVolume for every single replica you spin up. If replica zero dies, the control plane makes sure that the replacement replica zero binds back to that exact same storage volume, preventing split-brain scenarios and data loss. Understanding this binding lifecycle is the key to designing resilient architecture that can survive infrastructure failures without human intervention.
Here is a production-grade example of a StatefulSet configuration that properly maintains state across pod deletions and rescheduling events:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: secure-database
namespace: production
spec:
serviceName: "database-internal"
replicas: 1
selector:
matchLabels:
app: secure-database
template:
metadata:
labels:
app: secure-database
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secrets
key: password
volumeMounts:
- name: database-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: database-storage
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "gp3-encrypted"
resources:
requests:
storage: 50Gi
This configuration leverages a volumeClaimTemplates block, which instructs the Kubernetes control plane to automatically generate a unique, persistent storage volume for each pod managed by the StatefulSet. The ReadWriteOnce access mode ensures that only a single node can mount the volume at any given time, preventing dangerous concurrent write operations from multiple pods. By tying the storage lifecycle directly to the StatefulSet controller rather than an ephemeral deployment pod, we guarantee that deleting the pod will never result in the deletion of our underlying data volume.
Step-by-Step: Let's Build It Together
Implementing a truly resilient stateful architecture requires more than just a StatefulSet manifest; you also need a proper StorageClass and access control policies. Let us walk through building a complete, production-ready storage pipeline from scratch, ensuring every component is locked down and verified.
First, we need to define a custom StorageClass that specifies our cloud provider parameters, such as encryption at rest and high-performance provisioning. This tells the cluster how to provision the underlying disk when a persistent volume claim is submitted by our application.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-encrypted
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
reclaimPolicy: Retain
allowVolumeExpansion: true
That configuration provisions high-performance encrypted block storage with a Retain reclamation policy, ensuring that even if the persistent volume claim is accidentally deleted, the underlying AWS EBS volume remains intact in your cloud account.
Next, we deploy our application using the StatefulSet pattern we discussed earlier, ensuring it points to our newly created StorageClass. We also add liveness and readiness probes to make sure Kubernetes does not restart the container prematurely during heavy database recovery operations.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: resilient-cache
namespace: production
spec:
serviceName: "cache-service"
replicas: 2
selector:
matchLabels:
app: resilient-cache
template:
metadata:
labels:
app: resilient-cache
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
volumeMounts:
- name: redis-data
mountPath: /data
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 15
periodSeconds: 10
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "gp3-encrypted"
resources:
requests:
storage: 10Gi
By deploying this second manifest, we have successfully established a two-replica Redis cluster where each instance maintains its own dedicated, encrypted, and persistent block storage volume. When you delete either pod using kubectl, Kubernetes safely tears down the container instance, waits for it to terminate, and spins up a brand-new container that immediately reclaims the exact same persistent volume.
The Mistakes That Will Burn You
Even experienced engineers occasionally fall into traps when managing Kubernetes storage. Avoiding these common anti-patterns will save you from catastrophic data loss incidents during high-pressure production deployments.
- Mistake 1: Using emptyDir for application logs or caches that you actually care about. When the pod restarts, your data vanishes completely without warning.
- Mistake 2: Setting the storage reclaim policy to Delete on critical production PersistentVolumes. If a junior engineer accidentally deletes a PVC, your cloud provider instantly destroys the underlying disk and all associated backups.
- Mistake 3: Deploying databases inside standard Deployments with ReadWriteMany shared volumes. Multiple database instances writing to the same network file system simultaneously will cause database lock corruption and split-brain failures.
Production Checklist
Before you sign off on deploying your stateful workloads to production, run through this verification checklist to ensure your storage layer is fully bulletproof.
- Verify StorageClass policies: Ensure your production storage classes use the Retain reclaim policy rather than Delete to prevent accidental cloud disk termination.
- Test pod deletion recovery: Deliberately delete your stateful pods in a staging environment and verify that data persists seamlessly upon recreation.
- Implement automated backups: Never rely solely on Kubernetes persistent volumes for disaster recovery; always maintain independent, encrypted database snapshots.
- Never use standard deployments for databases: Always utilize StatefulSets when managing workloads that require stable network identities and persistent storage binding.
Key Takeaways
- Standard Deployments treat pods as ephemeral cattle, meaning pod deletion often destroys attached local storage.
- StatefulSets guarantee stable network identities and consistent PersistentVolumeClaim bindings across pod rescheduling events.
- Always configure your custom StorageClasses with the Retain reclamation policy to protect against accidental PVC deletions.
- Rigorously test your disaster recovery and pod failover procedures in a staging cluster before pushing stateful changes to production.
Engr. Hamza | AI & MLOps Engineer | Building autonomous systems at the edge of possibility


Top comments (0)