This makes it so when calling `docker run --rm`, or `docker rm -v`, only
volumes specified without a name, e.g. `docker run -v /foo` instead of
`docker run -v awesome:/foo` are removed.
Note that all volumes are named, some are named by the user, some get a
generated name. This is specifically about how the volume was specified
on `run`, assuming that if the user specified it with a name they expect
it to persist after the container is cleaned up.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -25,6 +25,11 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool) |
| 25 | 25 |
} |
| 26 | 26 |
daemon.volumes.Dereference(m.Volume, container.ID) |
| 27 | 27 |
if rm {
|
| 28 |
+ // Do not remove named mountpoints |
|
| 29 |
+ // these are mountpoints specified like `docker run -v <name>:/foo` |
|
| 30 |
+ if m.Named {
|
|
| 31 |
+ continue |
|
| 32 |
+ } |
|
| 28 | 33 |
err := daemon.volumes.Remove(m.Volume) |
| 29 | 34 |
// Ignore volume in use errors because having this |
| 30 | 35 |
// volume being referenced by other container is |
| ... | ... |
@@ -90,6 +90,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 90 | 90 |
Driver: m.Driver, |
| 91 | 91 |
Destination: m.Destination, |
| 92 | 92 |
Propagation: m.Propagation, |
| 93 |
+ Named: m.Named, |
|
| 93 | 94 |
} |
| 94 | 95 |
|
| 95 | 96 |
if len(cp.Source) == 0 {
|
| ... | ... |
@@ -126,6 +127,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 126 | 126 |
bind.Source = v.Path() |
| 127 | 127 |
// bind.Name is an already existing volume, we need to use that here |
| 128 | 128 |
bind.Driver = v.DriverName() |
| 129 |
+ bind.Named = true |
|
| 129 | 130 |
bind = setBindModeIfNull(bind) |
| 130 | 131 |
} |
| 131 | 132 |
if label.RelabelNeeded(bind.Mode) {
|
| ... | ... |
@@ -45,3 +45,17 @@ This command will delete all stopped containers. The command |
| 45 | 45 |
`docker ps -a -q` will return all existing container IDs and pass them to |
| 46 | 46 |
the `rm` command which will delete them. Any running containers will not be |
| 47 | 47 |
deleted. |
| 48 |
+ |
|
| 49 |
+ $ docker rm -v redis |
|
| 50 |
+ redis |
|
| 51 |
+ |
|
| 52 |
+This command will remove the container and any volumes associated with it. |
|
| 53 |
+Note that if a volume was specified with a name, it will not be removed. |
|
| 54 |
+ |
|
| 55 |
+ $ docker create -v awesome:/foo -v /bar --name hello redis |
|
| 56 |
+ hello |
|
| 57 |
+ $ docker rm -v hello |
|
| 58 |
+ |
|
| 59 |
+In this example, the volume for `/foo` will remain intact, but the volume for |
|
| 60 |
+`/bar` will be removed. The same behavior holds for volumes inherited with |
|
| 61 |
+`--volumes-from`. |
| ... | ... |
@@ -590,7 +590,11 @@ the container exits**, you can add the `--rm` flag: |
| 590 | 590 |
|
| 591 | 591 |
> **Note**: When you set the `--rm` flag, Docker also removes the volumes |
| 592 | 592 |
associated with the container when the container is removed. This is similar |
| 593 |
-to running `docker rm -v my-container`. |
|
| 593 |
+to running `docker rm -v my-container`. Only volumes that are specified without a |
|
| 594 |
+name are removed. For example, with |
|
| 595 |
+`docker run --rm -v /foo -v awesome:/bar busybox top`, the volume for `/foo` will be removed, |
|
| 596 |
+but the volume for `/bar` will not. Volumes inheritted via `--volumes-from` will be removed |
|
| 597 |
+with the same logic -- if the original volume was specified with a name it will **not** be removed. |
|
| 594 | 598 |
|
| 595 | 599 |
## Security configuration |
| 596 | 600 |
--security-opt="label:user:USER" : Set the label user for the container |
| ... | ... |
@@ -4137,3 +4137,42 @@ func (s *DockerSuite) TestRunNamedVolumeCopyImageData(c *check.C) {
|
| 4137 | 4137 |
out, _ := dockerCmd(c, "run", "-v", "foo:/foo", "busybox", "cat", "/foo/hello") |
| 4138 | 4138 |
c.Assert(strings.TrimSpace(out), check.Equals, "hello") |
| 4139 | 4139 |
} |
| 4140 |
+ |
|
| 4141 |
+func (s *DockerSuite) TestRunNamedVolumeNotRemoved(c *check.C) {
|
|
| 4142 |
+ prefix := "" |
|
| 4143 |
+ if daemonPlatform == "windows" {
|
|
| 4144 |
+ prefix = "c:" |
|
| 4145 |
+ } |
|
| 4146 |
+ |
|
| 4147 |
+ dockerCmd(c, "volume", "create", "--name", "test") |
|
| 4148 |
+ |
|
| 4149 |
+ dockerCmd(c, "run", "--rm", "-v", "test:"+prefix+"/foo", "-v", prefix+"/bar", "busybox", "true") |
|
| 4150 |
+ dockerCmd(c, "volume", "inspect", "test") |
|
| 4151 |
+ out, _ := dockerCmd(c, "volume", "ls", "-q") |
|
| 4152 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, "test") |
|
| 4153 |
+ |
|
| 4154 |
+ dockerCmd(c, "run", "--name=test", "-v", "test:"+prefix+"/foo", "-v", prefix+"/bar", "busybox", "true") |
|
| 4155 |
+ dockerCmd(c, "rm", "-fv", "test") |
|
| 4156 |
+ dockerCmd(c, "volume", "inspect", "test") |
|
| 4157 |
+ out, _ = dockerCmd(c, "volume", "ls", "-q") |
|
| 4158 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, "test") |
|
| 4159 |
+} |
|
| 4160 |
+ |
|
| 4161 |
+func (s *DockerSuite) TestRunNamedVolumesFromNotRemoved(c *check.C) {
|
|
| 4162 |
+ prefix := "" |
|
| 4163 |
+ if daemonPlatform == "windows" {
|
|
| 4164 |
+ prefix = "c:" |
|
| 4165 |
+ } |
|
| 4166 |
+ |
|
| 4167 |
+ dockerCmd(c, "volume", "create", "--name", "test") |
|
| 4168 |
+ dockerCmd(c, "run", "--name=parent", "-v", "test:"+prefix+"/foo", "-v", prefix+"/bar", "busybox", "true") |
|
| 4169 |
+ dockerCmd(c, "run", "--name=child", "--volumes-from=parent", "busybox", "true") |
|
| 4170 |
+ |
|
| 4171 |
+ // Remove the parent so there are not other references to the volumes |
|
| 4172 |
+ dockerCmd(c, "rm", "-f", "parent") |
|
| 4173 |
+ // now remove the child and ensure the named volume (and only the named volume) still exists |
|
| 4174 |
+ dockerCmd(c, "rm", "-fv", "child") |
|
| 4175 |
+ dockerCmd(c, "volume", "inspect", "test") |
|
| 4176 |
+ out, _ := dockerCmd(c, "volume", "ls", "-q") |
|
| 4177 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, "test") |
|
| 4178 |
+} |
| ... | ... |
@@ -228,6 +228,9 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
|
| 228 | 228 |
c.Assert(err, checker.IsNil, check.Commentf(out)) |
| 229 | 229 |
c.Assert(out, checker.Contains, s.server.URL) |
| 230 | 230 |
|
| 231 |
+ _, err = s.d.Cmd("volume", "rm", "external-volume-test")
|
|
| 232 |
+ c.Assert(err, checker.IsNil) |
|
| 233 |
+ |
|
| 231 | 234 |
p := hostVolumePath("external-volume-test")
|
| 232 | 235 |
_, err = os.Lstat(p) |
| 233 | 236 |
c.Assert(err, checker.NotNil) |
| ... | ... |
@@ -362,6 +365,9 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyE |
| 362 | 362 |
c.Fatal("volume creates fail when plugin not immediately available")
|
| 363 | 363 |
} |
| 364 | 364 |
|
| 365 |
+ _, err = s.d.Cmd("volume", "rm", "external-volume-test")
|
|
| 366 |
+ c.Assert(err, checker.IsNil) |
|
| 367 |
+ |
|
| 365 | 368 |
c.Assert(s.ec.activations, checker.Equals, 1) |
| 366 | 369 |
c.Assert(s.ec.creations, checker.Equals, 1) |
| 367 | 370 |
c.Assert(s.ec.removals, checker.Equals, 1) |
| ... | ... |
@@ -385,7 +391,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c |
| 385 | 385 |
c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver") |
| 386 | 386 |
} |
| 387 | 387 |
|
| 388 |
-func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverList(c *check.C) {
|
|
| 388 |
+func (s *DockerExternalVolumeSuite) TesttExternalVolumeDriverList(c *check.C) {
|
|
| 389 | 389 |
dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc") |
| 390 | 390 |
out, _ := dockerCmd(c, "volume", "ls") |
| 391 | 391 |
ls := strings.Split(strings.TrimSpace(out), "\n") |
| ... | ... |
@@ -399,7 +405,7 @@ func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverList(c *check.C |
| 399 | 399 |
c.Assert(s.ec.lists, check.Equals, 1) |
| 400 | 400 |
} |
| 401 | 401 |
|
| 402 |
-func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverGet(c *check.C) {
|
|
| 402 |
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(c *check.C) {
|
|
| 403 | 403 |
out, _, err := dockerCmdWithError("volume", "inspect", "dummy")
|
| 404 | 404 |
c.Assert(err, check.NotNil, check.Commentf(out)) |
| 405 | 405 |
c.Assert(s.ec.gets, check.Equals, 1) |
| ... | ... |
@@ -48,6 +48,22 @@ command. The use that name as follows: |
| 48 | 48 |
|
| 49 | 49 |
docker rm hopeful_morse |
| 50 | 50 |
|
| 51 |
+## Removing a container and all associated volumes |
|
| 52 |
+ |
|
| 53 |
+ $ docker rm -v redis |
|
| 54 |
+ redis |
|
| 55 |
+ |
|
| 56 |
+This command will remove the container and any volumes associated with it. |
|
| 57 |
+Note that if a volume was specified with a name, it will not be removed. |
|
| 58 |
+ |
|
| 59 |
+ $ docker create -v awesome:/foo -v /bar --name hello redis |
|
| 60 |
+ hello |
|
| 61 |
+ $ docker rm -v hello |
|
| 62 |
+ |
|
| 63 |
+In this example, the volume for `/foo` will remain in tact, but the volume for |
|
| 64 |
+`/bar` will be removed. The same behavior holds for volumes inherited with |
|
| 65 |
+`--volumes-from`. |
|
| 66 |
+ |
|
| 51 | 67 |
# HISTORY |
| 52 | 68 |
April 2014, Originally compiled by William Henry (whenry at redhat dot com) |
| 53 | 69 |
based on docker.com source material and internal work. |
| ... | ... |
@@ -59,6 +59,7 @@ type MountPoint struct {
|
| 59 | 59 |
|
| 60 | 60 |
// Note Propagation is not used on Windows |
| 61 | 61 |
Propagation string // Mount propagation string |
| 62 |
+ Named bool // specifies if the mountpoint was specified by name |
|
| 62 | 63 |
} |
| 63 | 64 |
|
| 64 | 65 |
// Setup sets up a mount point by either mounting the volume if it is |