Back to blog

Home Lab

Moving Kubernetes Storage to Longhorn: The PVC Was Not the Data

Migrating persistent workloads from node-bound storage to Longhorn was less about changing a StorageClass and more about proving that data, identity, and recovery still lined up afterward.

The first time a node went down, the problem was not that Kubernetes forgot where the application was. The problem was that the application’s data still belonged to the node it had left behind.

That was the point at which local persistent storage stopped being a harmless shortcut. Longhorn changed the relationship: storage became a cluster-managed resource that could be replicated, detached, and attached somewhere else.

The migration also exposed an easy misconception. A PVC is not a portable folder. It is a request and a binding. The data is a separate thing, with its own copy, consistency, and recovery concerns.

What changed

Before

The claim was satisfied by storage tied closely to a node or local path. A pod move could become a data-availability problem.

After

Longhorn owns the volume lifecycle, maintains replicas, and gives the workload a path to reattach on another node.

That is a better failure model, not a promise of unlimited resilience. Replicas placed on Kubernetes nodes that share one Proxmox host still share a physical failure domain.

The migration was a data move

Before creating anything, I inventoried claims, mount paths, access modes, application owners, and backup methods. A directory of uploaded files could be copied. A live database volume needed a database-consistent export. Treating both as “a PVC” would have made the most important decision invisible.

The cutover was deliberately boring: prepare the Longhorn volume, stop writes, transfer or restore the data, update the workload, and test the application from the outside. I kept the original volume during the rollback window instead of deleting it as soon as the new pod became ready.

The rule that saved the migration

Changing storageClassName creates a storage request. It does not move the bytes in an existing volume.

Longhorn added a new operating surface

Local storage hid decisions that Longhorn required me to make: replica count, node and disk scheduling, reserved capacity, recurring snapshots, backup targets, and what should happen when a replica becomes unhealthy.

The useful signals were no longer just “PVC bound” and “pod running.” I watched for degraded replicas, rebuild activity, capacity pressure, attachment failures, engine health, and backup freshness. A volume can be attached and serving traffic while still being one failure away from trouble.

Snapshots helped with short-term rollback. Backups to a separate target provided a different recovery boundary. I restored a volume into a clean workload and checked the application’s actual data rather than treating the existence of a snapshot as proof of recovery.

What I would do again

  • Test the old backup before starting the migration.
  • Move one workload at a time and keep the source volume during rollback.
  • Use application-aware exports for databases instead of copying live files.
  • Document Longhorn placement and backup policy in Git.
  • Test restore, not just provisioning and attachment.

Longhorn made the cluster more flexible, but the real improvement was being able to explain where state lived, what failure it could tolerate, and how to bring it back.

Further Reading

Longhorn documentation

Kubernetes persistent volumes