docker/cli · error
option 'bind-recursive=readonly' requires 'readonly' to be…
Error message
option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction
What it means
Returned by validateMountOptions (opts/mount_utils.go:25) when BindOptions.ReadOnlyForceRecursive is true (set by 'bind-recursive=readonly') but the mount is not marked read-only (m.ReadOnly is false). A recursively read-only bind mount requires the mount itself to be read-only, as the kernel feature (since Linux 5.12) makes the entire subtree read-only.
Solutions
- Add 'readonly' (or 'ro') to the --mount spec whenever using 'bind-recursive=readonly'.
- Verify the host kernel is >= 5.12 and Docker Engine >= v25 for recursively read-only mount support.
Example fix
// before: missing readonly // docker run --mount type=bind,src=/x,dst=/y,bind-recursive=readonly nginx // after: add readonly // docker run --mount type=bind,src=/x,dst=/y,readonly,bind-recursive=readonly nginx
Defensive patterns
Strategy: validation
Validate before calling
func validateRecursiveReadonly(spec string) error {
if strings.Contains(spec, "bind-recursive=readonly") &&
!strings.Contains(spec, "readonly") && !strings.Contains(spec, ",ro") &&
!strings.Contains(spec, "ro=") {
return fmt.Errorf("bind-recursive=readonly requires readonly to also be specified")
}
return nil
} Try / catch
if err := mountOpt.Set(value); err != nil {
if strings.Contains(err.Error(), "bind-recursive=readonly' requires 'readonly'") {
return fmt.Errorf("add 'readonly' to the mount spec when using bind-recursive=readonly")
}
return err
} Prevention
- Always include 'readonly' or 'ro' when using 'bind-recursive=readonly'.
- Verify kernel >= 5.12 and Docker Engine >= v25 for recursively read-only mount support.
- Review the bind-recursive documentation for required option combinations.
When it happens
Trigger: A --mount value includes 'bind-recursive=readonly' without also including 'readonly' or 'ro'. For example: '--mount type=bind,src=/x,dst=/y,bind-recursive=readonly'. validateMountOptions detects ReadOnlyForceRecursive=true with m.ReadOnly=false.
Common situations: Forgetting to add 'readonly' when wanting a recursively read-only bind mount, or misunderstanding that 'bind-recursive=readonly' forces the entire subtree read-only and thus requires the mount-level readonly flag.
Related errors
- option 'bind-recursive=writable' requires 'readonly' to be…
- option 'bind-recursive=readonly' requires…
- bind options are incompatible with type volume
- bind options are incompatible with type image
- invalid value for : (must be "enabled", "disabled"…
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/05c38b28b11eb81e.
Report an issue: GitHub.
Appendix: source
Thrown at opts/mount_utils.go:25
"github.com/moby/moby/api/types/mount"
)
// validateMountOptions performs client-side validation of mount options. Similar
// validation happens on the daemon side, but this validation allows us to
// produce user-friendly errors matching command-line options.
func validateMountOptions(m *mount.Mount) error {
if err := validateExclusiveOptions(m); err != nil {
return err
}
if m.BindOptions != nil {
if m.BindOptions.ReadOnlyNonRecursive && !m.ReadOnly {
return errors.New("option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction")
}
if m.BindOptions.ReadOnlyForceRecursive {
if !m.ReadOnly {
return errors.New("option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction")
}
if m.BindOptions.Propagation != mount.PropagationRPrivate {
// FIXME(thaJeztah): this is missing daemon-side validation
//
// docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly,readonly alpine
// # no error
return errors.New("option 'bind-recursive=readonly' requires 'bind-propagation=rprivate' to be specified in conjunction")
}
}
}
return nil
}
// validateExclusiveOptions checks if the given mount config only contains
// options for the given mount-type.
//
// This is the client-side equivalent of [mounts.validateExclusiveOptions] inView on GitHub (pinned to 4f84911bfe)