Browse code

Allow setting ulimits for containers

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2015/02/12 04:21:38
Showing 16 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/docker/docker/daemon/networkdriver"
7 7
 	"github.com/docker/docker/opts"
8 8
 	flag "github.com/docker/docker/pkg/mflag"
9
+	"github.com/docker/docker/pkg/ulimit"
9 10
 )
10 11
 
11 12
 const (
... ...
@@ -44,6 +45,7 @@ type Config struct {
44 44
 	Context                     map[string][]string
45 45
 	TrustKeyPath                string
46 46
 	Labels                      []string
47
+	Ulimits                     map[string]*ulimit.Ulimit
47 48
 }
48 49
 
49 50
 // InstallFlags adds command-line options to the top-level flag parser for
... ...
@@ -75,6 +77,8 @@ func (config *Config) InstallFlags() {
75 75
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
76 76
 	opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
77 77
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
78
+	config.Ulimits = make(map[string]*ulimit.Ulimit)
79
+	opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
78 80
 }
79 81
 
80 82
 func getDefaultNetworkMtu() int {
... ...
@@ -31,6 +31,7 @@ import (
31 31
 	"github.com/docker/docker/pkg/networkfs/resolvconf"
32 32
 	"github.com/docker/docker/pkg/promise"
33 33
 	"github.com/docker/docker/pkg/symlink"
34
+	"github.com/docker/docker/pkg/ulimit"
34 35
 	"github.com/docker/docker/runconfig"
35 36
 	"github.com/docker/docker/utils"
36 37
 )
... ...
@@ -276,11 +277,34 @@ func populateCommand(c *Container, env []string) error {
276 276
 		return err
277 277
 	}
278 278
 
279
+	var rlimits []*ulimit.Rlimit
280
+	ulimits := c.hostConfig.Ulimits
281
+
282
+	// Merge ulimits with daemon defaults
283
+	ulIdx := make(map[string]*ulimit.Ulimit)
284
+	for _, ul := range ulimits {
285
+		ulIdx[ul.Name] = ul
286
+	}
287
+	for name, ul := range c.daemon.config.Ulimits {
288
+		if _, exists := ulIdx[name]; !exists {
289
+			ulimits = append(ulimits, ul)
290
+		}
291
+	}
292
+
293
+	for _, limit := range ulimits {
294
+		rl, err := limit.GetRlimit()
295
+		if err != nil {
296
+			return err
297
+		}
298
+		rlimits = append(rlimits, rl)
299
+	}
300
+
279 301
 	resources := &execdriver.Resources{
280 302
 		Memory:     c.Config.Memory,
281 303
 		MemorySwap: c.Config.MemorySwap,
282 304
 		CpuShares:  c.Config.CpuShares,
283 305
 		Cpuset:     c.Config.Cpuset,
306
+		Rlimits:    rlimits,
284 307
 	}
285 308
 
286 309
 	processConfig := execdriver.ProcessConfig{
... ...
@@ -2,14 +2,16 @@ package execdriver
2 2
 
3 3
 import (
4 4
 	"errors"
5
-	"github.com/docker/docker/daemon/execdriver/native/template"
6
-	"github.com/docker/libcontainer"
7
-	"github.com/docker/libcontainer/devices"
8 5
 	"io"
9 6
 	"os"
10 7
 	"os/exec"
11 8
 	"strings"
12 9
 	"time"
10
+
11
+	"github.com/docker/docker/daemon/execdriver/native/template"
12
+	"github.com/docker/docker/pkg/ulimit"
13
+	"github.com/docker/libcontainer"
14
+	"github.com/docker/libcontainer/devices"
13 15
 )
14 16
 
15 17
 // Context is a generic key value pair that allows
... ...
@@ -99,10 +101,11 @@ type NetworkInterface struct {
99 99
 }
100 100
 
101 101
 type Resources struct {
102
-	Memory     int64  `json:"memory"`
103
-	MemorySwap int64  `json:"memory_swap"`
104
-	CpuShares  int64  `json:"cpu_shares"`
105
-	Cpuset     string `json:"cpuset"`
102
+	Memory     int64            `json:"memory"`
103
+	MemorySwap int64            `json:"memory_swap"`
104
+	CpuShares  int64            `json:"cpu_shares"`
105
+	Cpuset     string           `json:"cpuset"`
106
+	Rlimits    []*ulimit.Rlimit `json:"rlimits"`
106 107
 }
107 108
 
108 109
 type ResourceStats struct {
... ...
@@ -58,6 +58,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
58 58
 		return nil, err
59 59
 	}
60 60
 
61
+	d.setupRlimits(container, c)
62
+
61 63
 	cmds := make(map[string]*exec.Cmd)
62 64
 	d.Lock()
63 65
 	for k, v := range d.activeContainers {
... ...
@@ -172,6 +174,16 @@ func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.C
172 172
 	return err
173 173
 }
174 174
 
175
+func (d *driver) setupRlimits(container *libcontainer.Config, c *execdriver.Command) {
176
+	if c.Resources == nil {
177
+		return
178
+	}
179
+
180
+	for _, rlimit := range c.Resources.Rlimits {
181
+		container.Rlimits = append(container.Rlimits, libcontainer.Rlimit((*rlimit)))
182
+	}
183
+}
184
+
175 185
 func (d *driver) setupMounts(container *libcontainer.Config, c *execdriver.Command) error {
176 186
 	for _, m := range c.Mounts {
177 187
 		container.MountConfig.Mounts = append(container.MountConfig.Mounts, &mount.Mount{
... ...
@@ -74,6 +74,7 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.
74 74
 	if err := daemon.RegisterLinks(container, hostConfig); err != nil {
75 75
 		return err
76 76
 	}
77
+
77 78
 	container.hostConfig = hostConfig
78 79
 	container.toDisk()
79 80
 
... ...
@@ -51,6 +51,12 @@ You can still call an old version of the API using
51 51
 **New!**
52 52
 This endpoint now returns `Os`, `Arch` and `KernelVersion`.
53 53
 
54
+`POST /containers/create`
55
+`POST /containers/(id)/start`
56
+
57
+**New!**
58
+You can set ulimit settings to be used within the container.
59
+
54 60
 ## v1.17
55 61
 
56 62
 ### Full Documentation
... ...
@@ -86,7 +92,6 @@ root filesystem as read only.
86 86
 **New!**
87 87
 This endpoint returns a live stream of a container's resource usage statistics.
88 88
 
89
-
90 89
 ## v1.16
91 90
 
92 91
 ### Full Documentation
... ...
@@ -155,7 +155,8 @@ Create a container
155 155
                "CapDrop": ["MKNOD"],
156 156
                "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
157 157
                "NetworkMode": "bridge",
158
-               "Devices": []
158
+               "Devices": [],
159
+               "Ulimits": [{}]
159 160
             }
160 161
         }
161 162
 
... ...
@@ -244,6 +245,9 @@ Json Parameters:
244 244
   -   **Devices** - A list of devices to add to the container specified in the
245 245
         form
246 246
         `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
247
+  -   **Ulimits** - A list of ulimits to be set in the container, specified as
248
+        `{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
249
+        `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
247 250
 
248 251
 Query Parameters:
249 252
 
... ...
@@ -337,7 +341,8 @@ Return low-level information on the container `id`
337 337
 				"Name": "on-failure"
338 338
 			},
339 339
 			"SecurityOpt": null,
340
-			"VolumesFrom": null
340
+			"VolumesFrom": null,
341
+			"Ulimits": [{}]
341 342
 		},
342 343
 		"HostnamePath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hostname",
343 344
 		"HostsPath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hosts",
... ...
@@ -109,6 +109,7 @@ expect an integer, and they can only be specified once.
109 109
       --tlskey="~/.docker/key.pem"           Path to TLS key file
110 110
       --tlsverify=false                      Use TLS and verify the remote
111 111
       -v, --version=false                    Print version information and quit
112
+      --default-ulimit=[]                    Set default ulimit settings for containers.
112 113
 
113 114
 Options with [] may be specified multiple times.
114 115
 
... ...
@@ -404,6 +405,14 @@ This will only add the proxy and authentication to the Docker daemon's requests
404 404
 your `docker build`s and running containers will need extra configuration to use
405 405
 the proxy
406 406
 
407
+### Default Ulimits
408
+
409
+`--default-ulimit` allows you to set the default `ulimit` options to use for all
410
+containers. It takes the same options as `--ulimit` for `docker run`. If these
411
+defaults are not set, `ulimit` settings will be inheritted, if not set on
412
+`docker run`, from the Docker daemon. Any `--ulimit` options passed to
413
+`docker run` will overwrite these defaults.
414
+
407 415
 ### Miscellaneous options
408 416
 
409 417
 IP masquerading uses address translation to allow containers without a public IP to talk
... ...
@@ -1974,6 +1983,23 @@ You can add other hosts into a container's `/etc/hosts` file by using one or mor
1974 1974
 >      $ alias hostip="ip route show 0.0.0.0/0 | grep -Eo 'via \S+' | awk '{ print \$2 }'"
1975 1975
 >      $ docker run  --add-host=docker:$(hostip) --rm -it debian
1976 1976
 
1977
+### Setting ulimits in a container
1978
+
1979
+Since setting `ulimit` settings in a container requires extra privileges not
1980
+available in the default container, you can set these using the `--ulimit` flag.
1981
+`--ulimit` is specified with a soft and hard limit as such:
1982
+`<type>=<soft limit>[:<hard limit>]`, for example:
1983
+
1984
+```
1985
+    $ docker run --ulimit nofile=1024:1024 --rm debian ulimit -n
1986
+    1024
1987
+```
1988
+
1989
+>**Note:**
1990
+> If you do not provide a `hard limit`, the `soft limit` will be used for both
1991
+values. If no `ulimits` are set, they will be inherited from the default `ulimits`
1992
+set on the daemon.
1993
+
1977 1994
 ## save
1978 1995
 
1979 1996
     Usage: docker save [OPTIONS] IMAGE [IMAGE...]
... ...
@@ -480,3 +480,56 @@ func TestDaemonUpgradeWithVolumes(t *testing.T) {
480 480
 
481 481
 	logDone("daemon - volumes from old(pre 1.3) daemon work")
482 482
 }
483
+
484
+func TestDaemonUlimitDefaults(t *testing.T) {
485
+	d := NewDaemon(t)
486
+
487
+	if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil {
488
+		t.Fatal(err)
489
+	}
490
+
491
+	out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)")
492
+	if err != nil {
493
+		t.Fatal(out, err)
494
+	}
495
+
496
+	outArr := strings.Split(out, "\n")
497
+	if len(outArr) < 2 {
498
+		t.Fatal("got unexpected output: %s", out)
499
+	}
500
+	nofile := strings.TrimSpace(outArr[0])
501
+	nproc := strings.TrimSpace(outArr[1])
502
+
503
+	if nofile != "42" {
504
+		t.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile)
505
+	}
506
+	if nproc != "2048" {
507
+		t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
508
+	}
509
+
510
+	// Now restart daemon with a new default
511
+	if err := d.Restart("--default-ulimit", "nofile=43"); err != nil {
512
+		t.Fatal(err)
513
+	}
514
+
515
+	out, err = d.Cmd("start", "-a", "test")
516
+	if err != nil {
517
+		t.Fatal(err)
518
+	}
519
+
520
+	outArr = strings.Split(out, "\n")
521
+	if len(outArr) < 2 {
522
+		t.Fatal("got unexpected output: %s", out)
523
+	}
524
+	nofile = strings.TrimSpace(outArr[0])
525
+	nproc = strings.TrimSpace(outArr[1])
526
+
527
+	if nofile != "43" {
528
+		t.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile)
529
+	}
530
+	if nproc != "2048" {
531
+		t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
532
+	}
533
+
534
+	logDone("daemon - default ulimits are applied")
535
+}
... ...
@@ -91,3 +91,18 @@ func TestRunWithVolumesIsRecursive(t *testing.T) {
91 91
 
92 92
 	logDone("run - volumes are bind mounted recursively")
93 93
 }
94
+
95
+func TestRunWithUlimits(t *testing.T) {
96
+	defer deleteAllContainers()
97
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name=testulimits", "--ulimit", "nofile=42", "busybox", "/bin/sh", "-c", "ulimit -n"))
98
+	if err != nil {
99
+		t.Fatal(err, out)
100
+	}
101
+
102
+	ul := strings.TrimSpace(out)
103
+	if ul != "42" {
104
+		t.Fatalf("expected `ulimit -n` to be 42, got %s", ul)
105
+	}
106
+
107
+	logDone("run - ulimits are set")
108
+}
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/api"
12 12
 	flag "github.com/docker/docker/pkg/mflag"
13 13
 	"github.com/docker/docker/pkg/parsers"
14
+	"github.com/docker/docker/pkg/ulimit"
14 15
 	"github.com/docker/docker/utils"
15 16
 )
16 17
 
... ...
@@ -43,6 +44,10 @@ func LabelListVar(values *[]string, names []string, usage string) {
43 43
 	flag.Var(newListOptsRef(values, ValidateLabel), names, usage)
44 44
 }
45 45
 
46
+func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) {
47
+	flag.Var(NewUlimitOpt(values), names, usage)
48
+}
49
+
46 50
 // ListOpts type
47 51
 type ListOpts struct {
48 52
 	values    *[]string
49 53
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/docker/pkg/ulimit"
6
+)
7
+
8
+type UlimitOpt struct {
9
+	values map[string]*ulimit.Ulimit
10
+}
11
+
12
+func NewUlimitOpt(ref map[string]*ulimit.Ulimit) *UlimitOpt {
13
+	return &UlimitOpt{ref}
14
+}
15
+
16
+func (o *UlimitOpt) Set(val string) error {
17
+	l, err := ulimit.Parse(val)
18
+	if err != nil {
19
+		return err
20
+	}
21
+
22
+	o.values[l.Name] = l
23
+
24
+	return nil
25
+}
26
+
27
+func (o *UlimitOpt) String() string {
28
+	var out []string
29
+	for _, v := range o.values {
30
+		out = append(out, v.String())
31
+	}
32
+
33
+	return fmt.Sprintf("%v", out)
34
+}
35
+
36
+func (o *UlimitOpt) GetList() []*ulimit.Ulimit {
37
+	var ulimits []*ulimit.Ulimit
38
+	for _, v := range o.values {
39
+		ulimits = append(ulimits, v)
40
+	}
41
+
42
+	return ulimits
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,106 @@
0
+package ulimit
1
+
2
+import (
3
+	"fmt"
4
+	"strconv"
5
+	"strings"
6
+)
7
+
8
+// Human friendly version of Rlimit
9
+type Ulimit struct {
10
+	Name string
11
+	Hard int64
12
+	Soft int64
13
+}
14
+
15
+type Rlimit struct {
16
+	Type int    `json:"type,omitempty"`
17
+	Hard uint64 `json:"hard,omitempty"`
18
+	Soft uint64 `json:"soft,omitempty"`
19
+}
20
+
21
+const (
22
+	// magic numbers for making the syscall
23
+	// some of these are defined in the syscall package, but not all.
24
+	// Also since Windows client doesn't get access to the syscall package, need to
25
+	//	define these here
26
+	RLIMIT_AS         = 9
27
+	RLIMIT_CORE       = 4
28
+	RLIMIT_CPU        = 0
29
+	RLIMIT_DATA       = 2
30
+	RLIMIT_FSIZE      = 1
31
+	RLIMIT_LOCKS      = 10
32
+	RLIMIT_MEMLOCK    = 8
33
+	RLIMIT_MSGQUEUE   = 12
34
+	RLIMIT_NICE       = 13
35
+	RLIMIT_NOFILE     = 7
36
+	RLIMIT_NPROC      = 6
37
+	RLIMIT_RSS        = 5
38
+	RLIMIT_RTPRIO     = 14
39
+	RLIMIT_RTTIME     = 15
40
+	RLIMIT_SIGPENDING = 11
41
+	RLIMIT_STACK      = 3
42
+)
43
+
44
+var ulimitNameMapping = map[string]int{
45
+	//"as":         RLIMIT_AS, // Disbaled since this doesn't seem usable with the way Docker inits a container.
46
+	"core":       RLIMIT_CORE,
47
+	"cpu":        RLIMIT_CPU,
48
+	"data":       RLIMIT_DATA,
49
+	"fsize":      RLIMIT_FSIZE,
50
+	"locks":      RLIMIT_LOCKS,
51
+	"memlock":    RLIMIT_MEMLOCK,
52
+	"msgqueue":   RLIMIT_MSGQUEUE,
53
+	"nice":       RLIMIT_NICE,
54
+	"nofile":     RLIMIT_NOFILE,
55
+	"nproc":      RLIMIT_NPROC,
56
+	"rss":        RLIMIT_RSS,
57
+	"rtprio":     RLIMIT_RTPRIO,
58
+	"rttime":     RLIMIT_RTTIME,
59
+	"sigpending": RLIMIT_SIGPENDING,
60
+	"stack":      RLIMIT_STACK,
61
+}
62
+
63
+func Parse(val string) (*Ulimit, error) {
64
+	parts := strings.SplitN(val, "=", 2)
65
+	if len(parts) != 2 {
66
+		return nil, fmt.Errorf("invalid ulimit argument: %s", val)
67
+	}
68
+
69
+	if _, exists := ulimitNameMapping[parts[0]]; !exists {
70
+		return nil, fmt.Errorf("invalid ulimit type: %s", parts[0])
71
+	}
72
+
73
+	limitVals := strings.SplitN(parts[1], ":", 2)
74
+	if len(limitVals) > 2 {
75
+		return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
76
+	}
77
+
78
+	soft, err := strconv.ParseInt(limitVals[0], 10, 64)
79
+	if err != nil {
80
+		return nil, err
81
+	}
82
+
83
+	hard := soft // in case no hard was set
84
+	if len(limitVals) == 2 {
85
+		hard, err = strconv.ParseInt(limitVals[1], 10, 64)
86
+	}
87
+	if soft > hard {
88
+		return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, hard)
89
+	}
90
+
91
+	return &Ulimit{Name: parts[0], Soft: soft, Hard: hard}, nil
92
+}
93
+
94
+func (u *Ulimit) GetRlimit() (*Rlimit, error) {
95
+	t, exists := ulimitNameMapping[u.Name]
96
+	if !exists {
97
+		return nil, fmt.Errorf("invalid ulimit name %s", u.Name)
98
+	}
99
+
100
+	return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil
101
+}
102
+
103
+func (u *Ulimit) String() string {
104
+	return fmt.Sprintf("%s=%s:%s", u.Name, u.Soft, u.Hard)
105
+}
0 106
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package ulimit
1
+
2
+import "testing"
3
+
4
+func TestParseInvalidLimitType(t *testing.T) {
5
+	if _, err := Parse("notarealtype=1024:1024"); err == nil {
6
+		t.Fatalf("expected error on invalid ulimit type")
7
+	}
8
+}
9
+
10
+func TestParseBadFormat(t *testing.T) {
11
+	if _, err := Parse("nofile:1024:1024"); err == nil {
12
+		t.Fatal("expected error on bad syntax")
13
+	}
14
+
15
+	if _, err := Parse("nofile"); err == nil {
16
+		t.Fatal("expected error on bad syntax")
17
+	}
18
+
19
+	if _, err := Parse("nofile="); err == nil {
20
+		t.Fatal("expected error on bad syntax")
21
+	}
22
+	if _, err := Parse("nofile=:"); err == nil {
23
+		t.Fatal("expected error on bad syntax")
24
+	}
25
+	if _, err := Parse("nofile=:1024"); err == nil {
26
+		t.Fatal("expected error on bad syntax")
27
+	}
28
+}
29
+
30
+func TestParseHardLessThanSoft(t *testing.T) {
31
+	if _, err := Parse("nofile:1024:1"); err == nil {
32
+		t.Fatal("expected error on hard limit less than soft limit")
33
+	}
34
+}
35
+
36
+func TestParseInvalidValueType(t *testing.T) {
37
+	if _, err := Parse("nofile:asdf"); err == nil {
38
+		t.Fatal("expected error on bad value type")
39
+	}
40
+}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 
6 6
 	"github.com/docker/docker/engine"
7 7
 	"github.com/docker/docker/nat"
8
+	"github.com/docker/docker/pkg/ulimit"
8 9
 	"github.com/docker/docker/utils"
9 10
 )
10 11
 
... ...
@@ -119,6 +120,7 @@ type HostConfig struct {
119 119
 	RestartPolicy   RestartPolicy
120 120
 	SecurityOpt     []string
121 121
 	ReadonlyRootfs  bool
122
+	Ulimits         []*ulimit.Ulimit
122 123
 }
123 124
 
124 125
 // This is used by the create command when you want to set both the
... ...
@@ -156,6 +158,9 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
156 156
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
157 157
 	job.GetenvJson("Devices", &hostConfig.Devices)
158 158
 	job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
159
+
160
+	job.GetenvJson("Ulimits", &hostConfig.Ulimits)
161
+
159 162
 	hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
160 163
 	if Binds := job.GetenvList("Binds"); Binds != nil {
161 164
 		hostConfig.Binds = Binds
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/opts"
11 11
 	flag "github.com/docker/docker/pkg/mflag"
12 12
 	"github.com/docker/docker/pkg/parsers"
13
+	"github.com/docker/docker/pkg/ulimit"
13 14
 	"github.com/docker/docker/pkg/units"
14 15
 	"github.com/docker/docker/utils"
15 16
 )
... ...
@@ -32,6 +33,9 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
32 32
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
33 33
 		flDevices = opts.NewListOpts(opts.ValidatePath)
34 34
 
35
+		ulimits   = make(map[string]*ulimit.Ulimit)
36
+		flUlimits = opts.NewUlimitOpt(ulimits)
37
+
35 38
 		flPublish     = opts.NewListOpts(nil)
36 39
 		flExpose      = opts.NewListOpts(nil)
37 40
 		flDns         = opts.NewListOpts(opts.ValidateIPAddress)
... ...
@@ -82,6 +86,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
82 82
 	cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
83 83
 	cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
84 84
 	cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
85
+	cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
85 86
 
86 87
 	cmd.Require(flag.Min, 1)
87 88
 
... ...
@@ -309,6 +314,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
309 309
 		RestartPolicy:   restartPolicy,
310 310
 		SecurityOpt:     flSecurityOpt.GetAll(),
311 311
 		ReadonlyRootfs:  *flReadonlyRootfs,
312
+		Ulimits:         flUlimits.GetList(),
312 313
 	}
313 314
 
314 315
 	// When allocating stdin in attached mode, close stdin at client disconnect