Browse code

Merge pull request #13587 from rhatdan/volume-tmpfs

Add tmpfs as a valid volume source command.

David Calavera authored on 2015/12/03 04:16:49
Showing 24 changed files
... ...
@@ -66,6 +66,7 @@ RUN apt-get update && apt-get install -y \
66 66
 	ubuntu-zfs \
67 67
 	xfsprogs \
68 68
 	libzfs-dev \
69
+	tar \
69 70
 	--no-install-recommends \
70 71
 	&& ln -snf /usr/bin/clang-3.8 /usr/local/bin/clang \
71 72
 	&& ln -snf /usr/bin/clang++-3.8 /usr/local/bin/clang++
... ...
@@ -1394,6 +1394,7 @@ _docker_run() {
1394 1394
 		--restart
1395 1395
 		--security-opt
1396 1396
 		--stop-signal
1397
+		--tmpfs
1397 1398
 		--ulimit
1398 1399
 		--user -u
1399 1400
 		--uts
... ...
@@ -1443,7 +1444,7 @@ _docker_run() {
1443 1443
 			_filedir
1444 1444
 			return
1445 1445
 			;;
1446
-		--device|--volume|-v)
1446
+		--device|--tmpfs|--volume|-v)
1447 1447
 			case "$cur" in
1448 1448
 				*:*)
1449 1449
 					# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
... ...
@@ -339,6 +339,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'P
339 339
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal -d 'Signal to kill a container'
340 340
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY'
341 341
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
342
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l tmpfs -d 'Mount tmpfs on a directory'
342 343
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)'
343 344
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l volumes-from -d 'Mount volumes from the specified container(s)'
344 345
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
... ...
@@ -491,6 +491,7 @@ __docker_subcommand() {
491 491
         "($help)*--security-opt=[Security options]:security option: "
492 492
         "($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]"
493 493
         "($help -u --user)"{-u=,--user=}"[Username or UID]:user:_users"
494
+        "($help)--tmpfs[mount tmpfs] "
494 495
         "($help)*-v[Bind mount a volume]:volume: "
495 496
         "($help)--volume-driver=[Optional volume driver for the container]:volume driver:(local)"
496 497
         "($help)*--volumes-from=[Mount volumes from the specified container]:volume: "
... ...
@@ -1534,3 +1534,15 @@ func (container *Container) unmountVolumes(forceSyscall bool) error {
1534 1534
 
1535 1535
 	return nil
1536 1536
 }
1537
+
1538
+func (container *Container) tmpfsMounts() []execdriver.Mount {
1539
+	var mounts []execdriver.Mount
1540
+	for dest, data := range container.hostConfig.Tmpfs {
1541
+		mounts = append(mounts, execdriver.Mount{
1542
+			Source:      "tmpfs",
1543
+			Destination: dest,
1544
+			Data:        data,
1545
+		})
1546
+	}
1547
+	return mounts
1548
+}
... ...
@@ -191,6 +191,10 @@ func (container *Container) ipcMounts() []execdriver.Mount {
191 191
 	return nil
192 192
 }
193 193
 
194
+func (container *Container) tmpfsMounts() []execdriver.Mount {
195
+	return nil
196
+}
197
+
194 198
 func getDefaultRouteMtu() (int, error) {
195 199
 	return -1, errSystemNotSupported
196 200
 }
... ...
@@ -137,4 +137,5 @@ type CommonCommand struct {
137 137
 	Resources     *Resources    `json:"resources"`
138 138
 	Rootfs        string        `json:"rootfs"` // root fs of the container
139 139
 	WorkingDir    string        `json:"working_dir"`
140
+	TmpDir        string        `json:"tmpdir"` // Directory used to store docker tmpdirs.
140 141
 }
... ...
@@ -28,6 +28,7 @@ type Mount struct {
28 28
 	Writable    bool   `json:"writable"`
29 29
 	Private     bool   `json:"private"`
30 30
 	Slave       bool   `json:"slave"`
31
+	Data        string `json:"data"`
31 32
 }
32 33
 
33 34
 // Resources contains all resource configs for a driver.
... ...
@@ -4,10 +4,13 @@ package native
4 4
 
5 5
 import (
6 6
 	"fmt"
7
+	"path/filepath"
7 8
 	"strings"
8 9
 	"syscall"
9 10
 
10 11
 	"github.com/docker/docker/daemon/execdriver"
12
+	derr "github.com/docker/docker/errors"
13
+	"github.com/docker/docker/pkg/mount"
11 14
 
12 15
 	"github.com/opencontainers/runc/libcontainer/apparmor"
13 16
 	"github.com/opencontainers/runc/libcontainer/configs"
... ...
@@ -288,6 +291,36 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
288 288
 	container.Mounts = defaultMounts
289 289
 
290 290
 	for _, m := range c.Mounts {
291
+		for _, cm := range container.Mounts {
292
+			if cm.Destination == m.Destination {
293
+				return derr.ErrorCodeMountDup.WithArgs(m.Destination)
294
+			}
295
+		}
296
+
297
+		if m.Source == "tmpfs" {
298
+			var (
299
+				data  = "size=65536k"
300
+				flags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
301
+				err   error
302
+			)
303
+			fulldest := filepath.Join(c.Rootfs, m.Destination)
304
+			if m.Data != "" {
305
+				flags, data, err = mount.ParseTmpfsOptions(m.Data)
306
+				if err != nil {
307
+					return err
308
+				}
309
+			}
310
+			container.Mounts = append(container.Mounts, &configs.Mount{
311
+				Source:        m.Source,
312
+				Destination:   m.Destination,
313
+				Data:          data,
314
+				Device:        "tmpfs",
315
+				Flags:         flags,
316
+				PremountCmds:  genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination),
317
+				PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination),
318
+			})
319
+			continue
320
+		}
291 321
 		flags := syscall.MS_BIND | syscall.MS_REC
292 322
 		if !m.Writable {
293 323
 			flags |= syscall.MS_RDONLY
... ...
@@ -5,6 +5,7 @@ package native
5 5
 import (
6 6
 	"fmt"
7 7
 	"io"
8
+	"io/ioutil"
8 9
 	"os"
9 10
 	"os/exec"
10 11
 	"path/filepath"
... ...
@@ -128,6 +129,13 @@ type execOutput struct {
128 128
 // it calls libcontainer APIs to run a container.
129 129
 func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
130 130
 	destroyed := false
131
+	var err error
132
+	c.TmpDir, err = ioutil.TempDir("", c.ID)
133
+	if err != nil {
134
+		return execdriver.ExitStatus{ExitCode: -1}, err
135
+	}
136
+	defer os.RemoveAll(c.TmpDir)
137
+
131 138
 	// take the Command and populate the libcontainer.Config from it
132 139
 	container, err := d.createContainer(c, hooks)
133 140
 	if err != nil {
134 141
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package native
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"os/exec"
6
+	"strings"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+	"github.com/opencontainers/runc/libcontainer/configs"
10
+)
11
+
12
+func genTmpfsPremountCmd(tmpDir string, fullDest string, dest string) []configs.Command {
13
+	var premount []configs.Command
14
+	tarPath, err := exec.LookPath("tar")
15
+	if err != nil {
16
+		logrus.Warn("tar command is not available for tmpfs mount: %s", err)
17
+		return premount
18
+	}
19
+	if _, err = exec.LookPath("rm"); err != nil {
20
+		logrus.Warn("rm command is not available for tmpfs mount: %s", err)
21
+		return premount
22
+	}
23
+	tarFile := fmt.Sprintf("%s/%s.tar", tmpDir, strings.Replace(dest, "/", "_", -1))
24
+	if _, err := os.Stat(fullDest); err == nil {
25
+		premount = append(premount, configs.Command{
26
+			Path: tarPath,
27
+			Args: []string{"-cf", tarFile, "-C", fullDest, "."},
28
+		})
29
+	}
30
+	return premount
31
+}
32
+
33
+func genTmpfsPostmountCmd(tmpDir string, fullDest string, dest string) []configs.Command {
34
+	var postmount []configs.Command
35
+	tarPath, err := exec.LookPath("tar")
36
+	if err != nil {
37
+		return postmount
38
+	}
39
+	rmPath, err := exec.LookPath("rm")
40
+	if err != nil {
41
+		return postmount
42
+	}
43
+	if _, err := os.Stat(fullDest); os.IsNotExist(err) {
44
+		return postmount
45
+	}
46
+	tarFile := fmt.Sprintf("%s/%s.tar", tmpDir, strings.Replace(dest, "/", "_", -1))
47
+	postmount = append(postmount, configs.Command{
48
+		Path: tarPath,
49
+		Args: []string{"-xf", tarFile, "-C", fullDest, "."},
50
+	})
51
+	return append(postmount, configs.Command{
52
+		Path: rmPath,
53
+		Args: []string{"-f", tarFile},
54
+	})
55
+}
... ...
@@ -130,6 +130,7 @@ func (daemon *Daemon) containerStart(container *Container) (err error) {
130 130
 		return err
131 131
 	}
132 132
 	mounts = append(mounts, container.ipcMounts()...)
133
+	mounts = append(mounts, container.tmpfsMounts()...)
133 134
 
134 135
 	container.command.Mounts = mounts
135 136
 	if err := daemon.waitForStart(container); err != nil {
... ...
@@ -121,7 +121,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
121 121
 		}
122 122
 
123 123
 		if binds[bind.Destination] {
124
-			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
124
+			return derr.ErrorCodeMountDup.WithArgs(bind.Destination)
125 125
 		}
126 126
 
127 127
 		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
... ...
@@ -153,6 +153,14 @@ flag exists to allow special use-cases, like running Docker within Docker.
153 153
 The `-w` lets the command being executed inside directory given, here
154 154
 `/path/to/dir/`. If the path does not exists it is created inside the container.
155 155
 
156
+### mount tmpfs (--tmpfs)
157
+
158
+    $ docker run -d --tmpfs /run:rw,noexec,nosuid,size=65536k my_image
159
+
160
+    The --tmpfs flag mounts a tmpfs into the container with the rw,noexec,nosuid,size=65536k options.
161
+
162
+    Underlying content from the /run in the my_image image is copied into tmpfs.
163
+
156 164
 ### Mount volume (-v, --read-only)
157 165
 
158 166
     $ docker  run  -v `pwd`:`pwd` -w `pwd` -i -t  ubuntu pwd
... ...
@@ -1298,6 +1298,14 @@ above, or already defined by the developer with a Dockerfile `ENV`:
1298 1298
 
1299 1299
 Similarly the operator can set the **hostname** with `-h`.
1300 1300
 
1301
+### TMPFS (mount tmpfs filesystems)
1302
+
1303
+    --tmpfs=[]: Create a tmpfs mount with: container-dir[:<options>], where the options are identical to the Linux `mount -t tmpfs -o` command.
1304
+
1305
+    Underlying content from the "container-dir" is copied into tmpfs.
1306
+
1307
+    $ docker run -d --tmpfs /run:rw,noexec,nosuid,size=65536k my_image
1308
+
1301 1309
 ### VOLUME (shared filesystems)
1302 1310
 
1303 1311
     -v=[]: Create a bind mount with: [host-src:]container-dest[:<options>], where
... ...
@@ -444,12 +444,12 @@ var (
444 444
 		HTTPStatusCode: http.StatusInternalServerError,
445 445
 	})
446 446
 
447
-	// ErrorCodeVolumeDup is generated when we try to mount two volumes
447
+	// ErrorCodeMountDup is generated when we try to mount two mounts points
448 448
 	// to the same path.
449
-	ErrorCodeVolumeDup = errcode.Register(errGroup, errcode.ErrorDescriptor{
450
-		Value:          "VOLUMEDUP",
451
-		Message:        "Duplicate bind mount '%s'",
452
-		Description:    "An attempt was made to mount a volume but the specified destination location is already used in a previous mount",
449
+	ErrorCodeMountDup = errcode.Register(errGroup, errcode.ErrorDescriptor{
450
+		Value:          "MOUNTDUP",
451
+		Message:        "Duplicate mount point '%s'",
452
+		Description:    "An attempt was made to mount a content but the specified destination location is already used in a previous mount",
453 453
 		HTTPStatusCode: http.StatusInternalServerError,
454 454
 	})
455 455
 
... ...
@@ -212,7 +212,7 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) {
212 212
 	status, body, err := sockRequest("POST", "/containers/"+name+"/start", config)
213 213
 	c.Assert(err, checker.IsNil)
214 214
 	c.Assert(status, checker.Equals, http.StatusInternalServerError)
215
-	c.Assert(string(body), checker.Contains, "Duplicate bind", check.Commentf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err))
215
+	c.Assert(string(body), checker.Contains, "Duplicate mount point", check.Commentf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err))
216 216
 }
217 217
 
218 218
 func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
... ...
@@ -375,10 +375,10 @@ func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) {
375 375
 	mountstr2 := path2 + someplace
376 376
 
377 377
 	if out, _, err := dockerCmdWithError("run", "-v", mountstr1, "-v", mountstr2, "busybox", "true"); err == nil {
378
-		c.Fatal("Expected error about duplicate volume definitions")
378
+		c.Fatal("Expected error about duplicate mount definitions")
379 379
 	} else {
380
-		if !strings.Contains(out, "Duplicate bind mount") {
381
-			c.Fatalf("Expected 'duplicate volume' error, got %v", out)
380
+		if !strings.Contains(out, "Duplicate mount point") {
381
+			c.Fatalf("Expected 'duplicate mount point' error, got %v", out)
382 382
 		}
383 383
 	}
384 384
 }
... ...
@@ -438,3 +438,21 @@ func (s *DockerSuite) TestRunWithShmSize(c *check.C) {
438 438
 	c.Assert(err, check.IsNil)
439 439
 	c.Assert(shmSize, check.Equals, "1073741824")
440 440
 }
441
+
442
+func (s *DockerSuite) TestRunTmpfsMounts(c *check.C) {
443
+	// TODO Windows (Post TP4): This test cannot run on a Windows daemon as
444
+	// Windows does not support tmpfs mounts.
445
+	testRequires(c, DaemonIsLinux)
446
+	if out, _, err := dockerCmdWithError("run", "--tmpfs", "/run", "busybox", "touch", "/run/somefile"); err != nil {
447
+		c.Fatalf("/run directory not mounted on tmpfs %q %s", err, out)
448
+	}
449
+	if out, _, err := dockerCmdWithError("run", "--tmpfs", "/run:noexec,nosuid,rw,size=5k,mode=700", "busybox", "touch", "/run/somefile"); err != nil {
450
+		c.Fatalf("/run failed to mount on tmpfs with valid options %q %s", err, out)
451
+	}
452
+	if _, _, err := dockerCmdWithError("run", "--tmpfs", "/run:foobar", "busybox", "touch", "/run/somefile"); err == nil {
453
+		c.Fatalf("/run mounted on tmpfs when it should have vailed within invalid mount option")
454
+	}
455
+	if _, _, err := dockerCmdWithError("run", "--tmpfs", "/run", "-v", "/run:/run", "busybox", "touch", "/run/somefile"); err == nil {
456
+		c.Fatalf("Should have generated an error saying Duplicate mount  points")
457
+	}
458
+}
... ...
@@ -57,6 +57,7 @@ docker-create - Create a new container
57 57
 [**--stop-signal**[=*SIGNAL*]]
58 58
 [**--shm-size**[=*[]*]]
59 59
 [**-t**|**--tty**[=*false*]]
60
+[**--tmpfs**[=*[CONTAINER-DIR[:<OPTIONS>]*]]
60 61
 [**-u**|**--user**[=*USER*]]
61 62
 [**--ulimit**[=*[]*]]
62 63
 [**--uts**[=*[]*]]
... ...
@@ -271,6 +272,20 @@ This value should always larger than **-m**, so you should always use this with
271 271
 **-t**, **--tty**=*true*|*false*
272 272
    Allocate a pseudo-TTY. The default is *false*.
273 273
 
274
+**--tmpfs**=[] Create a tmpfs mount
275
+
276
+   Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
277
+
278
+   $ docker run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
279
+
280
+   This command mounts a `tmpfs` at `/tmp` within the container. The mount copies
281
+the underlying content of `my_image` into `/tmp`. For example if there was a
282
+directory `/tmp/content` in the base image, docker will copy this directory and
283
+all of its content on top of the tmpfs mounted on `/tmp`.  The supported mount
284
+options are the same as the Linux default `mount` flags. If you do not specify
285
+any options, the systems uses the following options:
286
+`rw,noexec,nosuid,nodev,size=65536k`.
287
+
274 288
 **-u**, **--user**=""
275 289
    Username or UID
276 290
 
... ...
@@ -60,6 +60,7 @@ docker-run - Run a command in a new container
60 60
 [**--shm-size**[=*[]*]]
61 61
 [**--sig-proxy**[=*true*]]
62 62
 [**-t**|**--tty**[=*false*]]
63
+[**--tmpfs**[=*[CONTAINER-DIR[:<OPTIONS>]*]]
63 64
 [**-u**|**--user**[=*USER*]]
64 65
 [**-v**|**--volume**[=*[]*]]
65 66
 [**--ulimit**[=*[]*]]
... ...
@@ -436,6 +437,20 @@ interactive shell. The default is false.
436 436
 The **-t** option is incompatible with a redirection of the docker client
437 437
 standard input.
438 438
 
439
+**--tmpfs**=[] Create a tmpfs mount
440
+
441
+   Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
442
+
443
+   $ docker run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
444
+
445
+   This command mounts a `tmpfs` at `/tmp` within the container. The mount copies
446
+the underlying content of `my_image` into `/tmp`. For example if there was a
447
+directory `/tmp/content` in the base image, docker will copy this directory and
448
+all of its content on top of the tmpfs mounted on `/tmp`.  The supported mount
449
+options are the same as the Linux default `mount` flags. If you do not specify
450
+any options, the systems uses the following options:
451
+`rw,noexec,nosuid,nodev,size=65536k`.
452
+
439 453
 **-u**, **--user**=""
440 454
    Sets the username or UID used and optionally the groupname or GID for the specified command.
441 455
 
... ...
@@ -552,6 +567,19 @@ the exit codes follow the `chroot` standard, see below:
552 552
 
553 553
 # EXAMPLES
554 554
 
555
+## Running container in read-only mode
556
+
557
+During container image development, containers often need to write to the image
558
+content.  Installing packages into /usr, for example.  In production,
559
+applications seldom need to write to the image.  Container applications write
560
+to volumes if they need to write to file systems at all.  Applications can be
561
+made more secure by running them in read-only mode using the --read-only switch.
562
+This protects the containers image from modification. Read only containers may
563
+still need to write temporary data.  The best way to handle this is to mount
564
+tmpfs directories on /run and /tmp.
565
+
566
+    # docker run --read-only --tmpfs /run --tmpfs /tmp -i -t fedora /bin/bash
567
+
555 568
 ## Exposing log messages from the container to the host's log
556 569
 
557 570
 If you want messages that are logged in your container to show up in the host's
... ...
@@ -1,6 +1,7 @@
1 1
 package mount
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"strings"
5 6
 )
6 7
 
... ...
@@ -67,3 +68,24 @@ func parseOptions(options string) (int, string) {
67 67
 	}
68 68
 	return flag, strings.Join(data, ",")
69 69
 }
70
+
71
+// ParseTmpfsOptions parse fstab type mount options into flags and data
72
+func ParseTmpfsOptions(options string) (int, string, error) {
73
+	flags, data := parseOptions(options)
74
+	validFlags := map[string]bool{
75
+		"size":      true,
76
+		"mode":      true,
77
+		"uid":       true,
78
+		"gid":       true,
79
+		"nr_inodes": true,
80
+		"nr_blocks": true,
81
+		"mpol":      true,
82
+	}
83
+	for _, o := range strings.Split(data, ",") {
84
+		opt := strings.SplitN(o, "=", 2)
85
+		if !validFlags[opt[0]] {
86
+			return 0, "", fmt.Errorf("Invalid tmpfs option %q", opt)
87
+		}
88
+	}
89
+	return flags, data, nil
90
+}
... ...
@@ -217,6 +217,7 @@ type HostConfig struct {
217 217
 	PublishAllPorts bool                  // Should docker publish all exposed port for the container
218 218
 	ReadonlyRootfs  bool                  // Is the container root filesystem in read-only
219 219
 	SecurityOpt     []string              // List of string values to customize labels for MLS systems, such as SELinux.
220
+	Tmpfs           map[string]string     `json:",omitempty"` // List of tmpfs (mounts) used for the container
220 221
 	UTSMode         UTSMode               // UTS namespace to use for the container
221 222
 	ShmSize         *int64                // Total shm memory usage
222 223
 
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/opts"
9 9
 	flag "github.com/docker/docker/pkg/mflag"
10
+	"github.com/docker/docker/pkg/mount"
10 11
 	"github.com/docker/docker/pkg/nat"
11 12
 	"github.com/docker/docker/pkg/parsers"
12 13
 	"github.com/docker/docker/pkg/signal"
... ...
@@ -50,6 +51,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
50 50
 		// FIXME: use utils.ListOpts for attach and volumes?
51 51
 		flAttach            = opts.NewListOpts(opts.ValidateAttach)
52 52
 		flVolumes           = opts.NewListOpts(nil)
53
+		flTmpfs             = opts.NewListOpts(nil)
53 54
 		flBlkioWeightDevice = opts.NewWeightdeviceOpt(opts.ValidateWeightDevice)
54 55
 		flLinks             = opts.NewListOpts(opts.ValidateLink)
55 56
 		flEnv               = opts.NewListOpts(opts.ValidateEnv)
... ...
@@ -111,6 +113,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
111 111
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
112 112
 	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
113 113
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
114
+	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
114 115
 	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
115 116
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
116 117
 	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
... ...
@@ -221,6 +224,19 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
221 221
 		}
222 222
 	}
223 223
 
224
+	// Can't evalute options passed into --tmpfs until we actually mount
225
+	tmpfs := make(map[string]string)
226
+	for _, t := range flTmpfs.GetAll() {
227
+		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
228
+			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
229
+				return nil, nil, cmd, err
230
+			}
231
+			tmpfs[arr[0]] = arr[1]
232
+		} else {
233
+			tmpfs[arr[0]] = ""
234
+		}
235
+	}
236
+
224 237
 	var (
225 238
 		parsedArgs = cmd.Args()
226 239
 		runCmd     *stringutils.StrSlice
... ...
@@ -396,6 +412,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
396 396
 		Isolation:      IsolationLevel(*flIsolation),
397 397
 		ShmSize:        parsedShm,
398 398
 		Resources:      resources,
399
+		Tmpfs:          tmpfs,
399 400
 	}
400 401
 
401 402
 	// When allocating stdin in attached mode, close stdin at client disconnect