search⌘K
search the log…
writing
$sysadmin$cybersecurity$devops$thoughts
homelabs
brasil homelabkubecraft homelab
site
about mecontact

The first time storage bit me, I had Postgres running in a pod, I deleted the pod to restart it, and every table was gone when it came back. Not empty. Gone. Nothing warned me it was going to happen.

That is the whole reason this distinction exists. This post is the concept: what ephemeral and persistent storage are, how you ask for each, when to use which, and why the difference matters. The hands-on version, where you reproduce that failure on purpose and watch it not happen the second time, is the separate walkthrough linked at the bottom.

What it is

Two categories. The whole topic is knowing which one you are standing on.

Ephemeral storage lives and dies with the workload. Two kinds:

Persistent storage outlives the pod. Three objects working together:

StorageSurvives container restartSurvives pod delete
Writable layerNoNo
emptyDirYesNo
PVCYesYes

How to use it

Ephemeral storage you get by default. If you want a named scratch volume, that is an emptyDir on the pod:

yaml
volumes: - name: scratch emptyDir: {}

The volume starts empty and is scoped to the pod. Nothing to size, nothing to provision. When the pod goes, it goes.

Persistent storage is two steps. First you claim it:

yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pg-data spec: accessModes: [ReadWriteOnce] storageClassName: local-path resources: requests: storage: 1Gi

Three lines in there are doing the work. accessModes: [ReadWriteOnce] means the volume can be mounted read and write by one node at a time. Most block storage is like this, and it is the setting that later dictates how you can roll a pod that uses it. storageClassName: local-path picks which provisioner makes the disk. local-path is what k3s ships with, and it carves the volume out as a directory on the node. requests.storage: 1Gi is how much you are asking for.

Then you mount that claim into the pod like any other volume:

yaml
volumes: - name: pgdata persistentVolumeClaim: claimName: pg-data

Notice the pod references the claim by name. It never names a disk. The claim binds to a PV the StorageClass created, and the pod just asks for the claim. That indirection is the point: your workload does not need to know what storage is underneath it.

When to reach for which

The test I use is simple. If losing the contents would matter tomorrow, it needs a PVC. If I could throw it away and rebuild it without caring, ephemeral is fine.

Ephemeral (emptyDir or the writable layer) is for cache, temp files, a scratch area, or handing files between two containers in the same pod. Things you can regenerate.

Persistent (a PVC) is for databases, user uploads, anything with state you expect to still be there after a restart. If it holds the only copy of something, it goes on a PVC.

Why it matters

Kubernetes assumes your workload can die and come back anywhere. To make that safe, it treats the container filesystem as disposable. A web server that holds nothing in itself does not care. A database does.

Here is the difference made concrete, which is the whole shape of the walkthrough. Run Postgres on an emptyDir, create a table, delete the pod. The replacement pod comes up with a fresh empty directory, Postgres re-initializes from scratch, and the table is gone. Run the same Postgres on a PVC, create the same table, delete the pod. The replacement mounts the same claim, Postgres finds its existing data, and the table is still there. Same deletion, opposite result, and the only thing that changed was where the data was written.

The cost of not knowing is the story at the top. You do not find out your storage was ephemeral until something you needed is already gone.

Things that trip people up

The hands-on version: Walkthrough: proving ephemeral vs persistent storage with Postgres. Every command, the output you should see at each step, and a troubleshooting section for the parts that look broken but are not.

kubernetesk3sstoragedevops