hashicorp/nomad · error

invalid docker volume %q: %v

Error message

invalid docker volume %q: %v

What it means

containerBinds parses each entry of driverConfig.Volumes with parseVolumeSpec; this error means one of the user-specified volume bind strings is malformed. A valid spec is src:dst[:mode]; parse failures include empty paths, too many/few colon-separated parts, or an invalid mode.

Source

Thrown at drivers/docker/driver.go:794

	binds := []string{allocDirBind, taskLocalBind, secretDirBind}

	logsROFlag := "ro"
	if selinuxLabel != "" {
		// Apply SELinux Label to each built-in bind
		for i := range binds {
			binds[i] = fmt.Sprintf("%s:%s", binds[i], selinuxLabel)
		}
		logsROFlag = "ro," + selinuxLabel
	}
	allocLogsDirBind := fmt.Sprintf("%s/logs:%s/logs:%s", task.TaskDir().SharedAllocDir, task.Env[taskenv.AllocDir], logsROFlag)
	binds = append(binds, allocLogsDirBind)

	for _, userbind := range driverConfig.Volumes {
		// This assumes host OS = docker container OS.
		// Not true, when we support Linux containers on Windows
		src, dst, mode, err := parseVolumeSpec(userbind, runtime.GOOS)
		if err != nil {
			return nil, fmt.Errorf("invalid docker volume %q: %v", userbind, err)
		}

		// Paths inside task dir are always allowed when using the default driver,
		// Relative paths are always allowed as they mount within a container
		// When a VolumeDriver is set, we assume we receive a binding in the format
		// volume-name:container-dest
		// Otherwise, we assume we receive a relative path binding in the format
		// relative/to/task:/also/in/container
		if taskLocalBindVolume {
			src = expandPath(task.TaskDir().Dir, src)
		} else {
			// Resolve dotted path segments
			src = filepath.Clean(src)
		}

		if !d.config.Volumes.Enabled {
			if err := escapingfs.ChildEscapesParentDir(task.AllocDir, src); err != nil {
				return nil, fmt.Errorf("volumes are not enabled; cannot mount host path: %q", userbind)

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Use the full three-part form: "host-src:container-dest:mode", at minimum "src:dst".
  2. Ensure both src and dst are non-empty absolute or relative paths.
  3. Limit mode to valid values ("ro", "rw", selinux labels like "z", "Z").
  4. On Windows, be careful with drive letters (C:\...) interacting with the spec parser.

Example fix

// before
config { volumes = ["/data"] }
// after
config { volumes = ["/host/data:/data:rw"] }
Defensive patterns

Strategy: validation

Validate before calling

func validBindSpec(spec string) bool {
	parts := strings.Split(spec, ":")
	if len(parts) < 2 || len(parts) > 3 { return false }
	if parts[0] == "" || parts[1] == "" { return false }
	if len(parts) == 3 {
		switch parts[2] { case "ro", "rw", "z", "Z", "": default: return false }
	}
	return true
}

Prevention

When it happens

Trigger: A config.volumes entry fails parseVolumeSpec — e.g. "only-one-path", "src:dst:mode:extra", empty source/destination, or a mode string that isn't ro/rw/z etc., for the host GOOS.

Common situations: Missing destination path ("/data" instead of "/data:/data"), Windows drive letters colliding with colon splitting, typos like "host/path:ctr/path:read-only", or quoting issues in HCL producing wrong strings.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/c95bd2feb599da39. Report an issue: GitHub.