The Error MessageIt is a common headache: you deploy a stateful application, but your PersistentVolumeClaim (PVC) refuses to budge from a Pending state. When you dig deeper using kubectl describe pvc <pvc-name>, you hit this specific wall:
persistentvolumeclaims "my-pvc" is forbidden: no persistent volumes available for this claim and no storage class is set
What is actually happening?Kubernetes is essentially telling you it has no idea where to put your data. Think of a PVC as a ticket for a seat at a theater. If the theater hasn't built any seats (Static Provisioning) and there is no automated machine to build one for you (Dynamic Provisioning), you are left standing in the lobby.
This error typically crops up for three reasons:
- Missing Default: Your cluster lacks a designated "Default" StorageClass.- Empty Manifests: Your PVC YAML doesn't specify a
storageClassName, leaving Kubernetes to guess.- Manual Mismatch: You created a PersistentVolume (PV) manually, but its size (e.g., 5Gi) or AccessMode doesn't match what the PVC asked for (e.g., 10Gi).## Solution 1: Assign a Default StorageClassMost managed services like AWS EKS or Google GKE come with storage drivers pre-installed, but they might not be set as the default. By marking a class as default, any PVC that doesn't ask for a specific type will automatically use this one. Start by listing your available classes:
kubectl get storageclass
If you see a class like gp2 (on AWS) or standard (on GKE) but it doesn't say (default), run this command to fix it:
kubectl patch storageclass gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
Once patched, delete your stuck PVC and recreate it. It should bind almost instantly.
Solution 2: Explicitly Define storageClassNameSometimes you don't want a global default. You might need high-performance SSDs for a database but cheap HDD storage for logs. In this case, you must tell the PVC exactly which "template" to use.
Update your pvc.yaml to include the storageClassName field:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: premium-r3 # Use your specific class name here
resources:
requests:
storage: 20Gi
Apply the update. Kubernetes will now reach out to the specific CSI driver (like the EBS or Azure Disk driver) to provision that 20Gi volume.
Solution 3: Manual Provisioning for Bare MetalIf you are running a home lab or a bare-metal cluster without a cloud provider, dynamic provisioning isn't available out of the box. You have to be the "storage machine" yourself.
Create a manual PersistentVolume that matches your claim's requirements:
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-01
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: "/mnt/data/mysql"
Pro tip: For the PVC to claim this manual volume, ensure the storageClassName in your PVC is either empty ("") or matches the class name you give to the PV.
Verifying the FixAfter applying one of these fixes, check your PVC status again:
kubectl get pvc
You are looking for the Bound status. It looks like this:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
database-storage Bound pvc-8a7b6c... 20Gi RWO premium-r3 15s
If the status is Bound, your Pods will finally move from ContainerCreating to Running.

