Skip to main content
4 min read·711 words

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 /mnt as a hostPath volume. 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:

ValueBehavior
Disabled (default)No effect. Behaves the same as omitting the field.
IfPossibleAttempts to apply recursive read-only. Falls back to Disabled if the kernel or container runtime does not support the feature.
EnabledRequires 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: true is set on the same volume mount.
  • mountPropagation is unset, or set to None. (HostToContainer and Bidirectional are 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​

StageKubernetes VersionFeature Gate Default
Alpha1.30false (opt-in required)
Beta1.31true (enabled by default)
Stable1.33Locked — 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: true is absent — recursiveReadOnly: Enabled requires readOnly: true; omitting readOnly causes the pod to fail.

References​

This article is based on information from the following official sources:

  1. Volumes — Recursive read-only mounts - Kubernetes Documentation
  2. Feature Gates - Kubernetes Documentation