docker/cli · error

option 'bind-recursive=writable' requires 'readonly' to be…

Error message

option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction

What it means

Returned by validateMountOptions (opts/mount_utils.go:21) when BindOptions.ReadOnlyNonRecursive is true (set by 'bind-recursive=writable') but the mount is not also marked read-only (m.ReadOnly is false). The 'writable' recursive mode is specifically designed for the read-only bind-mount behavior of Docker v24, so it only makes sense in conjunction with readonly.

Solutions

  1. Add 'readonly' (or 'ro') to the --mount spec whenever using 'bind-recursive=writable'.
  2. If you don't need read-only behavior, remove 'bind-recursive=writable' entirely (the default is recursive writable for non-readonly mounts).

Example fix

// before: missing readonly
// docker run --mount type=bind,src=/x,dst=/y,bind-recursive=writable nginx

// after: add readonly
// docker run --mount type=bind,src=/x,dst=/y,readonly,bind-recursive=writable nginx
Defensive patterns

Strategy: validation

Validate before calling

func validateRecursiveWritable(spec string) error {
    if strings.Contains(spec, "bind-recursive=writable") &&
        !strings.Contains(spec, "readonly") && !strings.Contains(spec, ",ro") &&
        !strings.Contains(spec, "ro=") {
        return fmt.Errorf("bind-recursive=writable requires readonly to also be specified")
    }
    return nil
}

Try / catch

if err := mountOpt.Set(value); err != nil {
    if strings.Contains(err.Error(), "bind-recursive=writable' requires 'readonly'") {
        return fmt.Errorf("add 'readonly' to the mount spec when using bind-recursive=writable")
    }
    return err
}

Prevention

When it happens

Trigger: A --mount value includes 'bind-recursive=writable' but does not also include 'readonly' or 'ro'. For example: '--mount type=bind,src=/x,dst=/y,bind-recursive=writable'. validateMountOptions detects ReadOnlyNonRecursive=true with m.ReadOnly=false.

Common situations: Misunderstanding that 'bind-recursive=writable' describes the behavior of read-only mounts that are recursively mounted but not recursively read-only — using it without readonly is contradictory.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/ecead33664ed9135. Report an issue: GitHub.

Appendix: source

Thrown at opts/mount_utils.go:21

import (
	"errors"
	"fmt"
	"strings"

	"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
}

View on GitHub (pinned to 4f84911bfe)