Every StorageClass on the cluster in one table. Use it to check which provisioner backs a class, whether it can grow, and what happens to the data when a PVC is deleted.
## The classes
| Class | Provisioner | Access modes | Binding | Reclaim | Expandable | Backed by |
| -------------------- | ------------------- | ------------- | -------------------- | ------- | ---------- | ------------------------------------- |
| `standard` (default) | `csi.hetzner.cloud` | RWO | WaitForFirstConsumer | Retain | Yes | Hetzner Cloud volumes, cloud nodes |
| `local-nvme` | `topolvm.io` | RWO | WaitForFirstConsumer | Retain | Yes | NVMe disks on bare metal |
| `local-ssd` | `topolvm.io` | RWO | WaitForFirstConsumer | Retain | Yes | SATA SSD disks on bare metal |
| `local-hdd` | `topolvm.io` | RWO | WaitForFirstConsumer | Retain | Yes | HDD disks on bare metal |
| `juicefs` | `csi.juicefs.com` | RWX, RWO, ROX | Immediate | Retain | Yes | Object storage plus a metadata engine |
`standard` is present and set as default on every cluster. The `local-*` classes appear once you [install TopoLVM](/docs/hetzner/apalla/storage/local/local-nvme-with-topolvm), and `juicefs` appears once you [install the JuiceFS CSI driver](/docs/hetzner/apalla/storage/shared/readwritemany-with-juicefs). List what your cluster has right now:
```console
$ kubectl get storageclasses
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-hdd topolvm.io Retain WaitForFirstConsumer true 10d
local-nvme topolvm.io Retain WaitForFirstConsumer true 10d
local-ssd topolvm.io Retain WaitForFirstConsumer true 10d
standard (default) csi.hetzner.cloud Retain WaitForFirstConsumer true 10d
```
## Reading the columns
**Access modes** decide how many nodes can mount a volume. The block and local classes are ReadWriteOnce; only `juicefs` offers ReadWriteMany. See [Access modes and volume binding](/docs/hetzner/apalla/storage/access-modes).
**Binding** is when the volume is created. WaitForFirstConsumer waits for a pod so the volume lands where the pod runs. Immediate creates it as soon as the PVC exists, which suits object-backed storage that has no location to match.
**Reclaim is Retain on every class, on purpose.** Deleting a PVC deletes the Kubernetes object, not the data behind it: the underlying volume is kept, not erased. Syself would rather a mistaken delete leave your data intact than let one command wipe it.
The flip side is that cleanup is a deliberate act. A Hetzner Cloud volume keeps costing money until you delete it yourself, and a local volume keeps holding pool space until you remove it. See [Volume attach constraints and reclaim policies](/docs/hetzner/apalla/storage/block/attach-constraints-and-reclaim) and [Local PV lifecycle](/docs/hetzner/apalla/storage/local/local-pv-lifecycle) for how to reclaim the space when you mean to.
> [!IMPORTANT]
> Deleting a PVC or PV never deletes the data on its own. If you truly want the data gone, delete the underlying Hetzner Cloud volume or remove the local logical volume as a separate, intentional step.
**Expandable** means you can grow a volume by editing the PVC. Every class allows expansion. Volumes only grow, they never shrink. See [Expand a Hetzner Cloud volume](/docs/hetzner/apalla/storage/block/expand-a-volume).
**Backed by** is the storage behind each class. `standard` is Hetzner Cloud volumes on cloud nodes: each volume sits on Ceph with three copies, so losing a disk does not lose the data. Hetzner attaches at most 16 volumes to one server, so plan for that ceiling on a volume-heavy node. The `local-*` classes are disks inside one bare metal server and carry no built-in redundancy, so keep the data replicated in the app.
## The default class and how to change it
A PVC with no `storageClassName` uses the default class, marked `(default)` in the list above.
The default is the one carrying this annotation:
```console
$ kubectl get storageclass standard -o jsonpath='{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}'
true
```
To make a different class the default, clear the flag on the current one and set it on the new one:
Clear the flag on the current default
```console title="Clear the current default"
$ kubectl patch storageclass standard -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
```
Set the flag on the new class
```console title="Set the new default"
$ kubectl patch storageclass local-nvme -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
```
> [!TIP]
> Prefer naming the class explicitly in each PVC (`storageClassName: local-nvme`) over relying on the default. It makes the intent obvious and survives a change to which class is default.