Kubernetes
GKE
PersistentVolumes
Helm
Cloud Storage

How can you reuse dynamically provisioned PersistentVolumes with Helm on GKE?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Reusing a dynamically provisioned PersistentVolume on GKE is possible, but it is not automatic. Kubernetes binds a PersistentVolumeClaim to a specific PersistentVolume, so successful reuse usually depends on reclaim policy, claim cleanup, and making the next claim point at the preserved volume deliberately.

Why Reuse Is Hard by Default

With dynamic provisioning, a PVC causes Kubernetes to create a new PV through a StorageClass. That is convenient for first-time installs, but it means a fresh Helm release often creates a fresh claim and therefore a fresh disk as well.

The reclaim policy is the first thing to check:

  • 'Delete usually removes the backing storage when the claim goes away.'
  • 'Retain keeps the backing volume so it can be reused later.'

If the original PV was created under a storage class whose reclaim policy is Delete, there may be nothing left to reuse after uninstall.

Preserve the Volume First

If you want data to survive Helm uninstall or PVC replacement, use or patch a Retain policy before deleting the claim:

bash
kubectl patch pv pvc-12345678-abcd \
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

After the old PVC is deleted, the PV may still carry the old claimRef. To make it available again, clear that reference:

bash
kubectl patch pv pvc-12345678-abcd --type=json \
  -p='[{"op":"remove","path":"/spec/claimRef"}]'

At that point the PV can return to an Available state.

Bind the New Claim to the Existing PV

When you want Helm to reuse the preserved volume, do not rely on ordinary dynamic provisioning. Prebind the claim to the PV by name and disable storage-class-based matching for that claim.

A Helm template can do this:

yaml
1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4  name: {{ .Values.persistence.claimName }}
5spec:
6  accessModes:
7    - ReadWriteOnce
8  resources:
9    requests:
10      storage: {{ .Values.persistence.size }}
11  storageClassName: ""
12  volumeName: {{ .Values.persistence.existingVolumeName }}

With values:

yaml
1persistence:
2  claimName: app-data
3  size: 10Gi
4  existingVolumeName: pvc-12345678-abcd

storageClassName: "" is important here because it prevents the claim from triggering a new dynamically provisioned volume while you are trying to bind to an existing one.

Separate First Install From Reuse

In many charts, the cleanest pattern is to support two modes:

  1. dynamic provisioning for the first install
  2. existing-volume binding for restore or reinstall

For example:

yaml
1{{- if .Values.persistence.existingVolumeName }}
2apiVersion: v1
3kind: PersistentVolumeClaim
4metadata:
5  name: {{ .Values.persistence.claimName }}
6spec:
7  accessModes:
8    - ReadWriteOnce
9  resources:
10    requests:
11      storage: {{ .Values.persistence.size }}
12  storageClassName: ""
13  volumeName: {{ .Values.persistence.existingVolumeName }}
14{{- else }}
15apiVersion: v1
16kind: PersistentVolumeClaim
17metadata:
18  name: {{ .Values.persistence.claimName }}
19spec:
20  accessModes:
21    - ReadWriteOnce
22  resources:
23    requests:
24      storage: {{ .Values.persistence.size }}
25  storageClassName: {{ .Values.persistence.storageClassName }}
26{{- end }}

That keeps reinstall behavior explicit instead of hoping Kubernetes will pick the old volume on its own.

GKE-Specific Reality

On GKE, the backing storage is often a Persistent Disk provisioned through a storage class. Reuse works, but only when the disk still exists and the PV is no longer reserved by an old claim. Helm itself does not manage this reclaim flow for you. It only renders the PVC manifests you tell it to render.

That is why volume reuse is partly a Kubernetes storage procedure and only partly a Helm chart design problem.

Common Pitfalls

The biggest mistake is uninstalling a release while the PV still uses a Delete reclaim policy. Once the backing disk is deleted, there is nothing to bind to later.

Another common issue is forgetting to clear claimRef. A retained PV can still stay stuck as bound to the old PVC until that reference is removed.

Teams also forget to set storageClassName: "" when reusing an existing PV. Without that, Kubernetes may provision a new disk instead of binding the old one.

Finally, be careful with data ownership. Reusing a volume means reusing old application state as well. That is useful for recovery, but dangerous if the new release expects a clean database.

Summary

  • Reuse starts with a retained PV; Delete policy usually destroys the backing storage.
  • After deleting the old PVC, clear the PV’s claimRef so it can become available again.
  • Bind the next PVC explicitly with volumeName.
  • Set storageClassName: "" when you want reuse instead of fresh dynamic provisioning.
  • Treat Helm as the PVC templating layer and Kubernetes as the system that actually controls PV reuse.

Course illustration
Course illustration

All Rights Reserved.