Recursive Read-Only Volume Mounts in Kubernetes
Required knowledge for the CKS certification.
Setting readOnly: true on a volume mount prevents a container from writing directly to the mounted directory. However, on Linux, read-only mounts are not recursively read-only by default. Any filesystem mounted beneath the read-only mount point — a tmpfs, an NFS share, a USB device, or another bind mount — remains fully writable inside the container. The recursiveReadOnly field, stable since Kubernetes 1.33, closes this gap by extending the read-only property to all submounts beneath the mount point.
1. The Security Problem
A plain readOnly: true mount protects the mount point itself but not submounts beneath it. According to the Kubernetes documentation:
On Linux, read-only mounts are not recursively read-only by default. For example, consider a Pod that mounts the host's
/mntas ahostPathvolume. If there is another filesystem mounted read-write on/mnt/<SUBMOUNT>(such as tmpfs, NFS, or USB storage), the volume mounted into the container(s) will also have a writeable/mnt/<SUBMOUNT>, even if the mount itself was specified as read-only.
This means a container process with access to a submount path can write files there despite the parent mount being read-only. In a hostPath scenario, this can expose host-level directories to writes originating inside the container.
2. The recursiveReadOnly Field
The recursiveReadOnly field is set per container in .spec.containers[*].volumeMounts[*].recursiveReadOnly. It accepts three values:
| Value | Behavior |
|---|---|
Disabled (default) | No effect. Behaves the same as omitting the field. |
IfPossible | Attempts to apply recursive read-only. Falls back to Disabled if the kernel or container runtime does not support the feature. |
Enabled | Requires recursive read-only. The Pod fails to start if any requirement is unmet. |
2.1 Requirements for Enabled
All of the following must be satisfied; the Pod fails if any condition is not met:
readOnly: trueis set on the same volume mount.mountPropagationis unset, or set toNone. (HostToContainerandBidirectionalare incompatible.)- The host is running Linux kernel v5.12 or later.
- The CRI-level container runtime supports recursive read-only mounts. Supported runtimes: containerd since v2.0, CRI-O since v1.30.
- The OCI-level container runtime supports recursive read-only mounts. Supported runtimes: runc since v1.1, crun since v1.8.6.
2.2 IfPossible fallback behavior
When IfPossible is used and the kernel or runtime does not support the feature, the field silently falls back to Disabled. The Pod still starts, but submounts remain writable. To confirm the effective setting, inspect the pod status (see Section 4).
For environments where the submount write path is a genuine security concern, use Enabled rather than IfPossible. Enabled fails loudly; IfPossible can silently yield no protection.
3. Configuration
3.1 Requiring recursive read-only
apiVersion: v1
kind: Pod
metadata:
name: secure-volume-pod
spec:
containers:
- name: app
image: registry.k8s.io/pause:3.10
volumeMounts:
- name: host-data
mountPath: /host-data
readOnly: true
mountPropagation: None
recursiveReadOnly: Enabled
volumes:
- name: host-data
hostPath:
path: /data
With Enabled, the pod fails to start if the kernel or runtime cannot satisfy the requirement. This is the appropriate setting when the no-write guarantee must be enforced.
3.2 Best-effort recursive read-only
volumeMounts:
- name: shared-config
mountPath: /etc/config
readOnly: true
recursiveReadOnly: IfPossible
With IfPossible, the pod starts on any cluster, but the protection is active only where the runtime supports it.
3.3 Incompatibility with Bidirectional mount propagation
Bidirectional mount propagation requires write access by design — it propagates mounts created inside the container back to the host. Setting recursiveReadOnly: Enabled alongside mountPropagation: Bidirectional on the same volume mount is rejected. The Kubernetes documentation notes separately that Bidirectional mount propagation can damage the host operating system and is allowed only in privileged containers. Do not combine the two fields on the same volume mount entry.
4. Verifying the Effective Setting
After the pod starts, check the kubelet-reported result in the pod status:
kubectl get pod <pod-name> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\n"}{range .volumeMounts[*]} {.name}: {.recursiveReadOnly}{"\n"}{end}{end}'
The .status.containerStatuses[*].volumeMounts[*].recursiveReadOnly field is set to either Enabled or Disabled by the kubelet, reflecting the actual state applied by the container runtime. A value of Disabled on a mount configured as IfPossible confirms the feature is not active on that node.
5. Version and Feature Gate History
| Stage | Kubernetes Version | Feature Gate Default |
|---|---|---|
| Alpha | 1.30 | false (opt-in required) |
| Beta | 1.31 | true (enabled by default) |
| Stable | 1.33 | Locked — always on |
The feature gate name is RecursiveReadOnlyMounts. As of Kubernetes 1.33, the gate is locked: setting it explicitly has no effect and produces no error. No gate configuration is required on 1.33 or later clusters.
6. Scope and Limitations
What this field protects: Submounts nested beneath a volume mount point within the container's mount namespace.
What it does not protect:
- Host processes with direct host filesystem access. The feature applies only within the container's mount namespace.
- Windows nodes. The feature is Linux-only.
- The case where
readOnly: trueis absent —recursiveReadOnly: EnabledrequiresreadOnly: true; omittingreadOnlycauses the pod to fail.
References
This article is based on information from the following official sources:
- Volumes — Recursive read-only mounts - Kubernetes Documentation
- Feature Gates - Kubernetes Documentation