A PVC that reads `Pending` is usually working exactly as designed. Two properties decide how a volume behaves: its access mode, which sets how many nodes can write it, and its binding mode, which sets when and where the volume is created. Get these right and most storage confusion disappears. ## Access modes: how many nodes can write A volume's access mode sets how many nodes can mount it at the same time. - **ReadWriteOnce (RWO):** one node mounts the volume read-write. Every pod that uses it must run on that node. Hetzner Cloud volumes and local TopoLVM volumes are RWO. - **ReadWriteMany (RWX):** many nodes mount the volume read-write at once. This needs a shared filesystem such as JuiceFS or NFS. Block and local volumes cannot do it. - **ReadOnlyMany (ROX):** many nodes mount the same volume read-only. Useful for static data that many pods read but none change. | Access mode | Nodes that can write | Example backend | | ------------------- | ----------------------------- | ----------------------------------- | | ReadWriteOnce (RWO) | One node | Hetzner Cloud volume, local TopoLVM | | ReadWriteMany (RWX) | Many nodes at once | JuiceFS, NFS | | ReadOnlyMany (ROX) | None, read-only on many nodes | Static data shared across pods | RWO is per node, not per pod. Several pods can share one RWO volume as long as they all run on the same node. Set a node affinity on the pods to keep them together. > [!NOTE] > Hetzner Cloud volumes are ReadWriteOnce only. To let pods on different nodes write the same directory, use [shared storage](/docs/hetzner/apalla/storage/shared/choose-an-rwx-backend). ## The pod is scheduled first, then the storage follows The most common storage mistake is thinking backwards about placement. People assume they pick where a volume lives on the PVC and the pod then schedules to meet it. It works the other way around: the scheduler places the pod first, and the storage has to be there. Which side leads depends on whether the volume exists yet: - **A new volume follows the pod.** With `WaitForFirstConsumer` binding, nothing is created until a pod that uses the PVC is scheduled. The scheduler picks the node, then the driver provisions the volume in that pod's topology: on that node for a local disk, in that location for a cloud volume. You never set the volume's place; the pod's placement sets it. This is zone-aware binding: the volume is created in the same location the pod runs, so a cloud volume never lands in a zone the pod cannot reach. - **An existing volume pins the pod.** Once a volume exists, it has a fixed home: a local volume lives on one server, a bound cloud volume lives in one Hetzner location. Kubernetes copies that home onto the pod as a volume node affinity, so the pod can only schedule where the volume already is. If that node is full, cordoned, or gone, the pod waits. The diagram shows both directions: ```mermaid flowchart TD subgraph fresh["A new volume follows the pod"] direction TB P1["Scheduler places the pod on a node"] --> V1["Driver provisions the volume
in the pod's node or location"] end subgraph bound["An existing volume pins the pod"] direction TB V2["Volume already lives on one node or location"] --> P2["Volume node affinity forces the pod there"] end ``` This is why a single-writer workload on a cloud or local volume can only run where its data is, and why moving that workload means moving or recreating the data with it. ## Binding: why Pending is normal A PVC binds to a real volume at one of two moments, set by the class's `volumeBindingMode`. - **WaitForFirstConsumer:** the volume is not created until a pod that uses the PVC is scheduled. Until then the PVC stays `Pending`. This is deliberate, not a failure, and it is what lets a new volume follow the pod. - **Immediate:** the volume is created as soon as the PVC exists, before any pod asks for it. The two timelines diverge on when the volume is created: ```mermaid flowchart LR subgraph wait["WaitForFirstConsumer"] direction TB W1["PVC created"] --> W2["Pending, no volume yet"] W2 --> W3["Pod that uses the PVC is scheduled"] W3 --> W4["Volume provisioned, PVC Bound"] end subgraph imm["Immediate"] direction TB I1["PVC created"] --> I2["Volume provisioned at once, PVC Bound"] end ``` The `standard` class and the local classes use `WaitForFirstConsumer`. A PVC you just created on these classes shows `Pending` and stays there until you schedule a pod that mounts it. Nothing is broken. ## When Immediate binding fits Object-backed volumes have no node or location to match, so they can bind immediately. JuiceFS uses Immediate binding: its data lives in object storage that every node can reach, so there is nothing to wait for, and its PVCs bind straight away instead of sitting `Pending`. A PVC that stays `Pending` even after its pod is scheduled is a different problem. See [Troubleshoot a stuck PVC](/docs/hetzner/apalla/storage/operations/troubleshoot-stuck-pvc) for the causes and fixes, and the [Storage classes reference](/docs/hetzner/apalla/storage/storage-classes-reference) for the binding mode of each class.