Access Modes (PV)
An access mode on a PV or PVC declares the concurrency contract under which the underlying storage may be mounted: which nodes may attach it, and in what mode? The Kubernetes documentation (k8s.io — Access Modes) defines four modes today — ReadWriteOnce (RWO), ReadOnlyMany (ROX), ReadWriteMany (RWX), and ReadWriteOncePod (RWOP, GA in 1.29 — k8s blog, Dec 2023). The mode is not a feature you implement on top of Kubernetes; it is a description of what the underlying storage backend (the CSI driver, ultimately the cloud or appliance) actually supports. RWO means “single node,” not “single Pod” — a foot-gun that the RWOP mode exists specifically to close. The mode is also not enforced by Kubernetes alone: the API server checks the PVC’s claimed mode against the PV’s offered modes at bind time, but the actual multi-attach prevention happens at the storage driver and the kubelet’s volume manager, not at the API layer. Understanding what each mode means at every layer of the stack — and what most cloud block storage actually supports — separates “I wrote
accessModes: [ReadWriteMany]and assumed it worked” from operational competence.
Mental Model
flowchart TB subgraph MODES["Four access modes"] RWO["**ReadWriteOnce**<br/>(RWO)<br/>1 node, RW<br/>multi-Pod-per-node OK"] ROX["**ReadOnlyMany**<br/>(ROX)<br/>N nodes, RO"] RWX["**ReadWriteMany**<br/>(RWX)<br/>N nodes, RW"] RWOP["**ReadWriteOncePod**<br/>(RWOP, GA 1.29)<br/>1 Pod (anywhere), RW<br/>strictly tighter than RWO"] end subgraph BACKENDS["What real storage supports"] BLOCK["**Block storage**<br/>EBS, GCE PD, Azure Disk<br/>(single attach)"] NFS["**NFS, EFS, CephFS,<br/>Azure Files, GlusterFS**<br/>(shared filesystem)"] OBJECT["**Object storage**<br/>S3, GCS, R2<br/>(via CSI POSIX layer)"] end BLOCK -- supports --> RWO BLOCK -- supports --> RWOP BLOCK -.usually NOT.-> RWX NFS -- supports --> RWX NFS -- supports --> RWO NFS -- supports --> ROX OBJECT -- "supports (slow)" --> RWX
What this diagram shows. The four modes are not all supported by every backend, and the matrix of “mode × backend” is what causes most operational surprises. Block storage (the cloud disk you get when you ask for a “volume” on AWS/GCP/Azure) can be attached to one VM at a time at the hypervisor level — that is the physical constraint that makes RWO the default and RWX impossible for that class of storage. Shared filesystems (NFS, EFS, CephFS, Azure Files) are designed for many-client concurrent access and naturally support RWX. RWOP is a software-only tightening of RWO that requires no new backend capability — any CSI driver that supports RWO can support RWOP — but it requires Kubernetes itself to enforce the single-Pod constraint at the API and kubelet layers. The insight to extract: the access mode is a declaration of the workload’s concurrency requirements; the cluster’s ability to fulfill it depends entirely on the StorageClass behind it.
Mechanical Walk-through
The four modes in detail
ReadWriteOnce (RWO)
“the volume can be mounted as read-write by a single node” — k8s.io docs.
The default access mode for all cloud block storage. The unit of exclusion is the Node, not the Pod: multiple Pods scheduled to the same node can all mount the same RWO PVC (they share the node’s mount). This is occasionally useful (sidecar Pods that need to inspect a database’s data directory) and occasionally a foot-gun (two replicas of the same StatefulSet should never be on the same node, but if your anti-affinity is preferred-not-required, they might be, and then they corrupt each other’s data because RWO didn’t stop them).
Backends: every cloud block volume (EBS, GCE PD, Azure Disk), every CSI driver for SAN/NAS that produces an iSCSI-like block volume. The vast majority of StatefulSet workloads use RWO PVCs.
ReadOnlyMany (ROX)
“the volume can be mounted read-only by many nodes.”
Designed for shared read-only datasets: a classifier model file consumed by 30 inference Pods across 5 nodes; a static asset bundle baked into a release artifact; a shared reference dataset for analytics. The volume itself must come from a backend that supports concurrent read access — typically NFS/CephFS, but also some block storage drivers that can attach a snapshot in read-only mode to multiple nodes (e.g., AWS EBS Multi-Attach with read-only mounts).
ROX is rarer in practice than RWO or RWX because most “shared read-only data” use cases are better served by ConfigMap (small), object storage with a CSI shim, or just baking the data into the container image.
ReadWriteMany (RWX)
“the volume can be mounted as read-write by many nodes.”
The mode for shared filesystems where multiple Pods on multiple nodes all read and write the same files concurrently. Use cases: a legacy app that expects a shared writable directory, a content-management system with a media-upload directory shared between web and worker Pods, a build-cache directory shared across CI Pods.
Backends: NFS, EFS, CephFS, GlusterFS, Azure Files, Portworx, OpenEBS Mayastor in shared mode. Cloud block storage does not support RWX in general — EBS Multi-Attach allows multiple attachments to nodes in the same AZ at the cost of giving up filesystem consistency (you must use a cluster-aware filesystem like GFS2, which K8s does not orchestrate for you).
RWX is the expensive mode: shared filesystems have non-trivial per-IOP latency, weaker consistency, and operational overhead compared to block storage. Production wisdom: if you think you need RWX, first ask whether the application can be refactored to put shared state in object storage or a database.
ReadWriteOncePod (RWOP) — GA in 1.29
“the volume can be mounted as read-write by a single Pod. Use ReadWriteOncePod access mode if you want to ensure that only one pod across the whole cluster can read that PVC or write to it.” — k8s.io docs, k8s blog (Dec 2023).
Strictly tighter than RWO. Where RWO permits multiple Pods on the same node to share the volume, RWOP forbids it — only one Pod, anywhere in the cluster, may have the PVC mounted at a time. The enforcement happens in two places:
- API-server admission: a ValidatingAdmissionWebhook-equivalent in the core API rejects creating a second Pod that references a PVC whose mode is
ReadWriteOncePodand that is already in use. - kubelet volume manager: the second-line defense if the first slipped (e.g., via direct etcd writes during a partition).
Stability timeline (KEP-2485): alpha in 1.22, beta in 1.27, GA in 1.29. Note that RWOP is only supported by CSI drivers — in-tree volume plugins, which were already legacy by the time RWOP shipped, are not extended to support it.
Use case archetypes:
- Single-writer databases. A Postgres primary that must never accidentally co-locate with another instance writing to the same PV. With RWO, a misconfigured rolling update could land both instances on the same node and corrupt the data directory; with RWOP, the API server refuses to start the second Pod.
- Migration patterns. When draining one node and starting on another, RWOP prevents the brief window where both old and new Pods could see the volume.
- Data-integrity-critical workloads generally: anything where the application assumes exclusive ownership and would corrupt data if that assumption fails.
Where the access mode is set
There are two places: the PVC and the PV. They must be compatible at bind time.
# In the PVC (what the user asks for)
spec:
accessModes: [ReadWriteOnce]
resources: { requests: { storage: 10Gi } }# In the PV (what the StorageClass-provisioned volume offers)
spec:
capacity: { storage: 10Gi }
accessModes: [ReadWriteOnce] # static PV authors set this manually;
# dynamic provisioners set it based on the
# driver's capabilities and the PVC's requestThe PVC controller binds a PVC to a PV iff: the PV’s accessModes[] is a superset of the PVC’s accessModes[], and capacity/storage class match. The PVC can list multiple modes, and the PV must offer all of them — this is the classic “promise” semantics where the PVC says “I’m fine with any of these” but the PV says “I offer all of these.” For dynamically provisioned PVCs, the CSI driver determines what mode the resulting PV gets based on the PVC’s request and the driver’s capabilities.
Listing access modes is a promise, not a request
This is the highest-impact subtlety in the entire access-mode topic. The PVC’s accessModes field lists modes the volume must support — but at mount time, the kubelet uses only one of them (typically the most permissive that fits the Pod’s intent). If you write accessModes: [ReadWriteMany] and bind to a PV that claims to offer RWX but is actually an EBS volume (the cloud provider doesn’t support RWX), you won’t notice until mount time on a second node, when the cloud API refuses to attach. The bind succeeds; the runtime fails. The Kubernetes docs are explicit (k8s.io): “a volume can only be mounted using one access mode at a time, even if it supports many. For example, a GCEPersistentDisk can be mounted as ReadWriteOnce by a single node or ReadOnlyMany by many nodes, but not at the same time.”
CSI access-mode mapping
The CSI spec defines a more granular set than the K8s API exposes — SINGLE_NODE_WRITER, SINGLE_NODE_READER_ONLY, MULTI_NODE_READER_ONLY, MULTI_NODE_SINGLE_WRITER, MULTI_NODE_MULTI_WRITER, plus the post-RWOP additions SINGLE_NODE_SINGLE_WRITER and SINGLE_NODE_MULTI_WRITER. The K8s access mode is a coarsening of these into the four user-facing modes. CSI driver authors map between them; the mapping table lives in the kubernetes-csi developer docs.
Configuration / API Surface
A PVC requesting RWO (the default)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: payments
spec:
storageClassName: gp3 # cloud block storage
accessModes:
- ReadWriteOnce # single node may mount RW
resources:
requests:
storage: 50GiA PVC requesting RWX (shared filesystem)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: media-uploads
namespace: cms
spec:
storageClassName: efs-sc # AWS EFS via CSI; supports RWX
accessModes:
- ReadWriteMany # multiple nodes may mount RW
resources:
requests:
storage: 100Gi # billing is by usage, not allocationIf you write this with storageClassName: gp3 (EBS), the PVC will bind to a PV that claims RWX but won’t mount on a second node. The error surfaces only when the second Pod tries to schedule. Diagnose with: kubectl describe pod <second-pod> → events show AttachVolume.Attach failed for volume "pv-...": rpc error: code = Internal desc = Could not attach volume "vol-..." to instance "i-...": VolumeInUse.
A PVC requesting RWOP (strictest)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: primary-pg
namespace: payments
spec:
storageClassName: gp3 # any CSI driver that supports RWO supports RWOP
accessModes:
- ReadWriteOncePod # only ONE Pod cluster-wide
resources:
requests:
storage: 100GiThe PV controller and admission plugin together enforce that no second Pod can use this PVC. If a user attempts:
kubectl run pg-rogue --image=postgres --overrides='{"spec":{"volumes":[{"name":"d","persistentVolumeClaim":{"claimName":"primary-pg"}}]}}'
# Error from server (Forbidden): pods "pg-rogue" is forbidden:
# PersistentVolumeClaim "primary-pg" with ReadWriteOncePod access mode is already used by pod "pg-primary-0"Inspecting modes on existing volumes
kubectl get pvc -n payments -o custom-columns=NAME:.metadata.name,MODES:.spec.accessModes,STATUS:.status.phase
# NAME MODES STATUS
# primary-pg [ReadWriteOncePod] Bound
# postgres-data [ReadWriteOnce] Bound
# media-uploads [ReadWriteMany] Bound
kubectl get pv -o custom-columns=NAME:.metadata.name,MODES:.spec.accessModes,CLASS:.spec.storageClassNameFailure Modes
- RWO + two replicas, two nodes. Common with StatefulSet when a replica is rescheduled before the old one’s volume is fully detached. Symptom: new Pod stuck in
ContainerCreatingwith eventAttachVolume.Attach failed: Multi-Attach error. Diagnostic:kubectl describe pod; resolution: wait (cloud APIs eventually detach), or force-delete the old Pod’svolumeattachmentobject if it’s truly orphaned. - RWO + two Pods, same node. No error — both Pods mount happily. This is “RWO is per-node, not per-Pod” biting; if you needed per-Pod exclusion you should have used RWOP.
- RWX requested, RWO-only backend bound. PVC binds; first Pod mounts; second Pod fails to schedule with
Multi-Attach error. The PVC’saccessModes: [RWX]lied about what the underlying volume supports. Fix: choose a StorageClass whose CSI driver actually supports RWX. - RWOP rejection during deployment. A
Deploymentwithreplicas: 2referencing an RWOP PVC: the first Pod creates fine, the second is rejected at admission. Symptom:ReplicaSetevents showpods "..." is forbidden. Fix: don’t use RWOP with stateless replicated workloads; RWOP is for StatefulSet singletons or operator-managed single-writer patterns. - EBS Multi-Attach + filesystem corruption. Some users enable EBS Multi-Attach and claim RWX. The block volume does attach to multiple nodes, but ext4/xfs are not cluster-aware — two nodes writing to the same filesystem will corrupt it. K8s does not protect against this; the CSI driver lets you walk into the trap. Production rule: never run RWX on a non-cluster-aware filesystem.
- In-tree driver + RWOP. Old clusters with in-tree volume plugins (pre-CSI-migration) can declare PVCs with RWOP, but the in-tree driver doesn’t enforce it. Migration to CSI is required for RWOP to actually do anything. Verify with
kubectl get csidrivers— the in-tree-replacement CSI drivers must be installed.
Alternatives and When to Choose Them
- No volume at all — use the database. “Shared writable directory” requirements often dissolve under scrutiny. If the data needs concurrent multi-writer access, a SQL or NoSQL database with proper concurrency control is almost always the right answer. RWX volumes are a legacy-app accommodation, not a target architecture.
- Object storage with per-Pod credentials. For shared blob-style data (uploads, ML datasets, build artifacts), a Pod-local mount of S3/GCS/R2 via a CSI driver (or just a sidecar) gives effectively-RWX semantics with cheaper per-IOP cost and stronger durability than NFS.
- Per-Pod RWO PVCs with replication at the app layer. Many workloads that seem to need shared storage really want replicated-but-isolated storage: each Pod gets its own RWO PVC, and the application or operator handles cross-replica state (think Postgres streaming replication, Cassandra, Kafka). StatefulSet + RWO PVCs is the canonical pattern.
- Pod-collocated emptyDir. For single-Pod workloads that need temporary shared scratch space across containers,
volumes: [emptyDir]is faster and simpler than any PVC — see Volume Types (Kubernetes). Not durable across Pod restarts.
Production Notes
- The single-writer database rule. Most production Postgres-on-K8s operators (CNPG, Zalando, CrunchyData) recommend (or require) RWOP for primary instances in K8s 1.29+ clusters. The data-integrity argument is decisive: if there’s even a one-second window where two Pods touch the data directory, the database is corrupt. RWO is not enough.
- EFS / Azure Files latency tax. Production write-ups (Spotify, Airbnb, Shopify infrastructure blogs) consistently note that RWX-backed workloads have latency profiles 10-100× worse than RWO-backed equivalents. Capacity planning for RWX-heavy workloads must include this; otherwise applications expecting “fast local disk” behavior will degrade unpredictably.
- The “I wrote RWX and it bound” trap. A recurring k8s.af failure mode: a user writes
accessModes: [ReadWriteMany], the PVC binds to an EBS-backed PV (because the cluster admins lied or were lazy in their StorageClass), the workload works fine until a Pod restart lands on a different node, and the deployment falls over in production. Catch this at admission with a Kyverno / OPA Gatekeeper policy that rejects RWX PVCs whose StorageClass is known to be RWO-only. - Multi-Attach EBS for clustered databases. Some HPC and clustered-database workloads (Oracle RAC) use EBS Multi-Attach with cluster filesystems (GFS2, OCFS2). This works but is operationally heavy and orthogonal to K8s — Kubernetes provides the attachment, the cluster filesystem provides the coordination.
See Also
- PersistentVolume — declares
accessModesas part of its offered capabilities - PersistentVolumeClaim — declares
accessModesas part of its request - StorageClass — implicitly determines what modes are achievable via its CSI driver
- Container Storage Interface — defines the underlying CSI access-mode capabilities
- Volume Health and Resize — sibling operational concern; access mode affects offline-resize feasibility
- StatefulSet — primary consumer of RWO and RWOP; rarely uses RWX
- Dynamic Volume Provisioning — the path that actually creates the PV with appropriate access modes
- Kubernetes MOC §7 — Storage