diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index c8ba4cd255..fc4ac855d5 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types/container" networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" @@ -31,6 +32,7 @@ type containerOptions struct { attach opts.ListOpts volumes opts.ListOpts tmpfs opts.ListOpts + mounts opts.MountOpt blkioWeightDevice opts.WeightdeviceOpt deviceReadBps opts.ThrottledeviceOpt deviceWriteBps opts.ThrottledeviceOpt @@ -223,6 +225,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory") flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)") flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume") + flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container") // Health-checking flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") @@ -321,6 +324,10 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) } + mounts := copts.mounts.Value() + if len(mounts) > 0 && copts.volumeDriver != "" { + logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.") + } var binds []string volumes := copts.volumes.GetMap() // add any bind targets to the list of container volumes @@ -589,6 +596,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err Tmpfs: tmpfs, Sysctls: copts.sysctls.GetAll(), Runtime: copts.runtime, + Mounts: mounts, } if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 51572eb92f..878670f939 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1518,6 +1518,7 @@ _docker_container_run_and_create() { --memory-swap --memory-swappiness --memory-reservation + --mount --name --network --network-alias diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index f3dddd9277..38957b8b77 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -138,6 +138,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l link -d 'Add complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s m -l memory -d 'Memory limit (format: [], where unit = b, k, m or g)' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l mac-address -d 'Container MAC address (e.g., 92:d0:c6:0a:29:33)' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l memory-swap -d "Total memory usage (memory + swap), set '-1' to disable swap (format: [], where unit = b, k, m or g)" +complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l mount -d 'Attach a filesystem mount to the container' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l name -d 'Assign a name to the container' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l net -d 'Set the Network mode for the container' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s P -l publish-all -d 'Publish all exposed ports to random ports on the host interfaces' @@ -330,6 +331,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add li complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: [], where unit = b, k, m or g)' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l mac-address -d 'Container MAC address (e.g., 92:d0:c6:0a:29:33)' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l memory-swap -d "Total memory usage (memory + swap), set '-1' to disable swap (format: [], where unit = b, k, m or g)" +complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l mount -d 'Attach a filesystem mount to the container' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l net -d 'Set the Network mode for the container' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to random ports on the host interfaces' diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 938a1f5af0..a791353a88 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -629,6 +629,7 @@ __docker_container_subcommand() { "($help)--log-driver=[Default driver for container logs]:logging driver:__docker_complete_log_drivers" "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_complete_log_options" "($help)--mac-address=[Container MAC address]:MAC address: " + "($help)*--mount=[Attach a filesystem mount to the container]:mount: " "($help)--name=[Container name]:name: " "($help)--network=[Connect a container to a network]:network mode:(bridge none container host)" "($help)*--network-alias=[Add network-scoped alias for the container]:alias: " diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 0fbe07fbe2..c2189b5ea8 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -85,6 +85,7 @@ Options: --memory-reservation string Memory soft limit --memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap --memory-swappiness int Tune container memory swappiness (0 to 100) (default -1) + --mount value Attach a filesytem mount to the container (default []) --name string Assign a name to the container --network-alias value Add network-scoped alias for the container (default []) --network string Connect a container to a network (default "default") diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 6a9bbd5459..e2e5d0a48c 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -95,6 +95,7 @@ Options: --memory-reservation string Memory soft limit --memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap --memory-swappiness int Tune container memory swappiness (0 to 100) (default -1) + --mount value Attach a filesystem mount to the container (default []) --name string Assign a name to the container --network-alias value Add network-scoped alias for the container (default []) --network string Connect a container to a network @@ -316,6 +317,29 @@ docker run -v c:\foo:c:\existing-directory-with-contents ... For in-depth information about volumes, refer to [manage data in containers](https://docs.docker.com/engine/tutorials/dockervolumes/) + +### Add bind-mounts or volumes using the --mount flag + +The `--mount` flag allows you to mount volumes, host-directories and `tmpfs` +mounts in a container. + +The `--mount` flag supports most options that are supported by the `-v` or the +`--volume` flag, but uses a different syntax. For in-depth information on the +`--mount` flag, and a comparison between `--volume` and `--mount`, refer to +the [service create command reference](service_create.md#add-bind-mounts-or-volumes). + +Even though there is no plan to deprecate `--volume`, usage of `--mount` is recommended. + +Examples: + +```bash +$ docker run --read-only --mount type=volume,target=/icanwrite busybox touch /icanwrite/here +``` + +```bash +$ docker run -t -i --mount type=bind,src=/data,dst=/data busybox sh +``` + ### Publish or expose port (-p, --expose) ```bash diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 80d9510528..e903e1a937 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -4422,6 +4422,184 @@ func (s *DockerSuite) TestRunMountReadOnlyDevShm(c *check.C) { c.Assert(out, checker.Contains, "Read-only file system") } +func (s *DockerSuite) TestRunMount(c *check.C) { + testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace) + + // mnt1, mnt2, and testCatFooBar are commonly used in multiple test cases + tmpDir, err := ioutil.TempDir("", "mount") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(tmpDir) + mnt1, mnt2 := path.Join(tmpDir, "mnt1"), path.Join(tmpDir, "mnt2") + if err := os.Mkdir(mnt1, 0755); err != nil { + c.Fatal(err) + } + if err := os.Mkdir(mnt2, 0755); err != nil { + c.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(mnt1, "test1"), []byte("test1"), 0644); err != nil { + c.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(mnt2, "test2"), []byte("test2"), 0644); err != nil { + c.Fatal(err) + } + testCatFooBar := func(cName string) error { + out, _ := dockerCmd(c, "exec", cName, "cat", "/foo/test1") + if out != "test1" { + return fmt.Errorf("%s not mounted on /foo", mnt1) + } + out, _ = dockerCmd(c, "exec", cName, "cat", "/bar/test2") + if out != "test2" { + return fmt.Errorf("%s not mounted on /bar", mnt2) + } + return nil + } + + type testCase struct { + equivalents [][]string + valid bool + // fn should be nil if valid==false + fn func(cName string) error + } + cases := []testCase{ + { + equivalents: [][]string{ + { + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/bar", mnt2), + }, + { + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=bind,src=%s,target=/bar", mnt2), + }, + { + "--volume", mnt1 + ":/foo", + "--mount", fmt.Sprintf("type=bind,src=%s,target=/bar", mnt2), + }, + }, + valid: true, + fn: testCatFooBar, + }, + { + equivalents: [][]string{ + { + "--mount", fmt.Sprintf("type=volume,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=volume,src=%s,dst=/bar", mnt2), + }, + { + "--mount", fmt.Sprintf("type=volume,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=volume,src=%s,target=/bar", mnt2), + }, + }, + valid: false, + }, + { + equivalents: [][]string{ + { + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=volume,src=%s,dst=/bar", mnt2), + }, + { + "--volume", mnt1 + ":/foo", + "--mount", fmt.Sprintf("type=volume,src=%s,target=/bar", mnt2), + }, + }, + valid: false, + fn: testCatFooBar, + }, + { + equivalents: [][]string{ + { + "--read-only", + "--mount", "type=volume,dst=/bar", + }, + }, + valid: true, + fn: func(cName string) error { + _, _, err := dockerCmdWithError("exec", cName, "touch", "/bar/icanwritehere") + return err + }, + }, + { + equivalents: [][]string{ + { + "--read-only", + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1), + "--mount", "type=volume,dst=/bar", + }, + { + "--read-only", + "--volume", fmt.Sprintf("%s:/foo", mnt1), + "--mount", "type=volume,dst=/bar", + }, + }, + valid: true, + fn: func(cName string) error { + out, _ := dockerCmd(c, "exec", cName, "cat", "/foo/test1") + if out != "test1" { + return fmt.Errorf("%s not mounted on /foo", mnt1) + } + _, _, err := dockerCmdWithError("exec", cName, "touch", "/bar/icanwritehere") + return err + }, + }, + { + equivalents: [][]string{ + { + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt2), + }, + { + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1), + "--mount", fmt.Sprintf("type=bind,src=%s,target=/foo", mnt2), + }, + { + "--volume", fmt.Sprintf("%s:/foo", mnt1), + "--mount", fmt.Sprintf("type=bind,src=%s,target=/foo", mnt2), + }, + }, + valid: false, + }, + { + equivalents: [][]string{ + { + "--volume", fmt.Sprintf("%s:/foo", mnt1), + "--mount", fmt.Sprintf("type=volume,src=%s,target=/foo", mnt2), + }, + }, + valid: false, + }, + { + equivalents: [][]string{ + { + "--mount", "type=volume,target=/foo", + "--mount", "type=volume,target=/foo", + }, + }, + valid: false, + }, + } + + for i, testCase := range cases { + for j, opts := range testCase.equivalents { + cName := fmt.Sprintf("mount-%d-%d", i, j) + _, _, err := dockerCmdWithError(append([]string{"run", "-i", "-d", "--name", cName}, + append(opts, []string{"busybox", "top"}...)...)...) + if testCase.valid { + c.Assert(err, check.IsNil, + check.Commentf("got error while creating a container with %v (%s)", opts, cName)) + c.Assert(testCase.fn(cName), check.IsNil, + check.Commentf("got error while executing test for %v (%s)", opts, cName)) + dockerCmd(c, "rm", "-f", cName) + } else { + c.Assert(err, checker.NotNil, + check.Commentf("got nil while creating a container with %v (%s)", opts, cName)) + } + } + } +} + // Test that passing a FQDN as hostname properly sets hostname, and // /etc/hostname. Test case for 29100 func (s *DockerSuite) TestRunHostnameFQDN(c *check.C) { diff --git a/man/docker-run.1.md b/man/docker-run.1.md index 0ee0a13ee8..257711d196 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -61,6 +61,7 @@ docker-run - Run a command in a new container [**--memory-reservation**[=*MEMORY-RESERVATION*]] [**--memory-swap**[=*LIMIT*]] [**--memory-swappiness**[=*MEMORY-SWAPPINESS*]] +[**--mount**[=*[MOUNT]*]] [**--name**[=*NAME*]] [**--network-alias**[=*[]*]] [**--network**[=*"bridge"*]] @@ -425,6 +426,42 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. The IPv6 link-local address will be based on the device's MAC address according to RFC4862. +**--mount**=[*[type=TYPE[,TYPE-SPECIFIC-OPTIONS]]*] + Attach a filesystem mount to the container + + Current supported mount `TYPES` are `bind`, `volume`, and `tmpfs`. + + e.g. + + `type=bind,source=/path/on/host,destination=/path/in/container` + + `type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red",volume-label="shape=round"` + + `type=tmpfs,tmpfs-size=512M,destination=/path/in/container` + + Common Options: + + * `src`, `source`: mount source spec for `bind` and `volume`. Mandatory for `bind`. + * `dst`, `destination`, `target`: mount destination spec. + * `ro`, `read-only`: `true` or `false` (default). + + Options specific to `bind`: + + * `bind-propagation`: `shared`, `slave`, `private`, `rshared`, `rslave`, or `rprivate`(default). See also `mount(2)`. + * `consistency`: `consistent`(default), `cached`, or `delegated`. Currently, only effective for Docker for Mac. + + Options specific to `volume`: + + * `volume-driver`: Name of the volume-driver plugin. + * `volume-label`: Custom metadata. + * `volume-nocopy`: `true`(default) or `false`. If set to `false`, the Engine copies existing files and directories under the mount-path into the volume, allowing the host to access them. + * `volume-opt`: specific to a given volume driver. + + Options specific to `tmpfs`: + + * `tmpfs-size`: Size of the tmpfs mount in bytes. Unlimited by default in Linux. + * `tmpfs-mode`: File mode of the tmpfs in octal. (e.g. `700` or `0700`.) Defaults to `1777` in Linux. + **--name**="" Assign a name to the container @@ -604,6 +641,9 @@ options are the same as the Linux default `mount` flags. If you do not specify any options, the systems uses the following options: `rw,noexec,nosuid,nodev,size=65536k`. + See also `--mount`, which is the successor of `--tmpfs` and `--volume`. + Even though there is no plan to deprecate `--tmpfs`, usage of `--mount` is recommended. + **-u**, **--user**="" Sets the username or UID used and optionally the groupname or GID for the specified command. @@ -704,6 +744,9 @@ change propagation properties of source mount. Say `/` is source mount for To disable automatic copying of data from the container path to the volume, use the `nocopy` flag. The `nocopy` flag can be set on bind mounts and named volumes. +See also `--mount`, which is the successor of `--tmpfs` and `--volume`. +Even though there is no plan to deprecate `--volume`, usage of `--mount` is recommended. + **--volume-driver**="" Container's volume driver. This driver creates volumes specified either from a Dockerfile's `VOLUME` instruction or from the `docker run -v` flag.