vps cluster docs

0005. Authoritative data on a separate volume at /srv/data, via static retained PVs

Context

Most data on this cluster is disposable: caches, a demo app’s sqlite file, an index that can be rebuilt. A few datasets are not: a tenant’s Postgres holding student data, a PostGIS database, and a MinIO bucket of uploaded files. Those need to survive a mistake, a bad migration, and — as far as possible — the loss of the node.

The cluster ran on dynamic local-path PVs, which means the data sat on the root disk alongside the OS, container images, container logs and the metrics TSDB. Every dynamic PV also had persistentVolumeReclaimPolicy: Delete, so deleting a PVC destroyed its data (ArgoCD pruning one would have too). And two StorageClasses both carried the “default” annotation, so a PVC that merely omitted storageClassName was placed by coin flip — which is exactly how one app ended up bound to a billed Hetzner volume that was later deleted, leaving it unable to start for weeks.

Decision

Authoritative data lives on one dedicated Hetzner volume mounted at /srv/data, exposed to the cluster as static hostPath PVs with Retain. Disposable data may use dynamic provisioning.

Alternatives rejected

Consequences

Evidence

terraform/storage.tf in the cluster checkout (cluster/terraform/storage.tf), ansible/roles/data_volume/, docs/storage.md (the per-dataset table), k8s/platform/storage/persistentvolumes.yaml, and the storage entries in changelog/2026-09.md.