diff --git a/daemon/container.go b/daemon/container.go
index c8cf9ef194..2a462692b8 100644
--- a/daemon/container.go
+++ b/daemon/container.go
@@ -79,6 +79,7 @@ type CommonContainer struct {
MountLabel, ProcessLabel string
RestartCount int
HasBeenStartedBefore bool
+ HasBeenManuallyStopped bool // used for unless-stopped restart policy
hostConfig *runconfig.HostConfig
command *execdriver.Command
monitor *containerMonitor
@@ -1063,6 +1064,7 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
func (container *Container) shouldRestart() bool {
return container.hostConfig.RestartPolicy.Name == "always" ||
+ (container.hostConfig.RestartPolicy.Name == "unless-stopped" && !container.HasBeenManuallyStopped) ||
(container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0)
}
diff --git a/daemon/daemon.go b/daemon/daemon.go
index 0fb8b0f2f3..979a3f0d1d 100644
--- a/daemon/daemon.go
+++ b/daemon/daemon.go
@@ -103,6 +103,7 @@ type Daemon struct {
EventsService *events.Events
netController libnetwork.NetworkController
root string
+ shutdown bool
}
// Get looks for a container using the provided information, which could be
@@ -744,6 +745,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
}
func (daemon *Daemon) Shutdown() error {
+ daemon.shutdown = true
if daemon.containers != nil {
group := sync.WaitGroup{}
logrus.Debug("starting clean shutdown of all containers...")
diff --git a/daemon/monitor.go b/daemon/monitor.go
index 1f020574b0..21fd9a6b68 100644
--- a/daemon/monitor.go
+++ b/daemon/monitor.go
@@ -119,6 +119,10 @@ func (m *containerMonitor) Start() error {
}
m.Close()
}()
+ // reset stopped flag
+ if m.container.HasBeenManuallyStopped {
+ m.container.HasBeenManuallyStopped = false
+ }
// reset the restart count
m.container.RestartCount = -1
@@ -223,11 +227,12 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool {
// do not restart if the user or docker has requested that this container be stopped
if m.shouldStop {
+ m.container.HasBeenManuallyStopped = !m.container.daemon.shutdown
return false
}
switch {
- case m.restartPolicy.IsAlways():
+ case m.restartPolicy.IsAlways(), m.restartPolicy.IsUnlessStopped():
return true
case m.restartPolicy.IsOnFailure():
// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
diff --git a/docs/reference/api/docker_remote_api_v1.21.md b/docs/reference/api/docker_remote_api_v1.21.md
index 7a3545a0e6..5cd2a00a41 100644
--- a/docs/reference/api/docker_remote_api_v1.21.md
+++ b/docs/reference/api/docker_remote_api_v1.21.md
@@ -276,7 +276,8 @@ Json Parameters:
- **Capdrop** - A list of kernel capabilities to drop from the container.
- **RestartPolicy** – The behavior to apply when the container exits. The
value is an object with a `Name` property of either `"always"` to
- always restart or `"on-failure"` to restart only when the container
+ always restart, `"unless-stopped"` to restart always except when
+ user has manually stopped the container or `"on-failure"` to restart only when the container
exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
controls the number of times to retry before giving up.
The default is not to restart. (optional)
diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md
index 8c093f18eb..9aa340e88f 100644
--- a/docs/reference/commandline/create.md
+++ b/docs/reference/commandline/create.md
@@ -58,7 +58,7 @@ Creates a new container.
--pid="" PID namespace to use
--privileged=false Give extended privileges to this container
--read-only=false Mount the container's root filesystem as read only
- --restart="no" Restart policy (no, on-failure[:max-retry], always)
+ --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped)
--security-opt=[] Security options
-t, --tty=false Allocate a pseudo-TTY
--disable-content-trust=true Skip image verification
diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md
index ab900d41b9..5791bd74d1 100644
--- a/docs/reference/commandline/run.md
+++ b/docs/reference/commandline/run.md
@@ -58,7 +58,7 @@ weight=1
--pid="" PID namespace to use
--privileged=false Give extended privileges to this container
--read-only=false Mount the container's root filesystem as read only
- --restart="no" Restart policy (no, on-failure[:max-retry], always)
+ --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped)
--rm=false Automatically remove the container when it exits
--security-opt=[] Security Options
--sig-proxy=true Proxy received signals to the process
@@ -440,7 +440,16 @@ Docker supports the following restart policies:
Always restart the container regardless of the exit status.
When you specify always, the Docker daemon will try to restart
- the container indefinitely.
+ the container indefinitely. The container will also always start
+ on daemon startup, regardless of the current state of the container.
+ |
+
+
+ unless-stopped |
+
+ Always restart the container regardless of the exit status, but
+ do not start it on daemon startup if the container has been put
+ to a stopped state before.
|
diff --git a/docs/reference/run.md b/docs/reference/run.md
index df3f656ac6..4fd2c0f0c2 100644
--- a/docs/reference/run.md
+++ b/docs/reference/run.md
@@ -398,7 +398,16 @@ Docker supports the following restart policies:
Always restart the container regardless of the exit status.
When you specify always, the Docker daemon will try to restart
- the container indefinitely.
+ the container indefinitely. The container will also always start
+ on daemon startup, regardless of the current state of the container.
+ |
+
+
+ unless-stopped |
+
+ Always restart the container regardless of the exit status, but
+ do not start it on daemon startup if the container has been put
+ to a stopped state before.
|
diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go
index b6bc10ee39..5cb9dbacd5 100644
--- a/integration-cli/docker_cli_daemon_test.go
+++ b/integration-cli/docker_cli_daemon_test.go
@@ -87,6 +87,60 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) {
}
}
+// #11008
+func (s *DockerDaemonSuite) TestDaemonRestartUnlessStopped(c *check.C) {
+ err := s.d.StartWithBusybox()
+ c.Assert(err, check.IsNil)
+
+ out, err := s.d.Cmd("run", "-d", "--name", "top1", "--restart", "always", "busybox:latest", "top")
+ c.Assert(err, check.IsNil, check.Commentf("run top1: %v", out))
+
+ out, err = s.d.Cmd("run", "-d", "--name", "top2", "--restart", "unless-stopped", "busybox:latest", "top")
+ c.Assert(err, check.IsNil, check.Commentf("run top2: %v", out))
+
+ testRun := func(m map[string]bool, prefix string) {
+ var format string
+ for name, shouldRun := range m {
+ out, err := s.d.Cmd("ps")
+ c.Assert(err, check.IsNil, check.Commentf("run ps: %v", out))
+ if shouldRun {
+ format = "%scontainer %q is not running"
+ } else {
+ format = "%scontainer %q is running"
+ }
+ c.Assert(strings.Contains(out, name), check.Equals, shouldRun, check.Commentf(format, prefix, name))
+ }
+ }
+
+ // both running
+ testRun(map[string]bool{"top1": true, "top2": true}, "")
+
+ out, err = s.d.Cmd("stop", "top1")
+ c.Assert(err, check.IsNil, check.Commentf(out))
+
+ out, err = s.d.Cmd("stop", "top2")
+ c.Assert(err, check.IsNil, check.Commentf(out))
+
+ // both stopped
+ testRun(map[string]bool{"top1": false, "top2": false}, "")
+
+ err = s.d.Restart()
+ c.Assert(err, check.IsNil)
+
+ // restart=always running
+ testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ")
+
+ out, err = s.d.Cmd("start", "top2")
+ c.Assert(err, check.IsNil, check.Commentf("start top2: %v", out))
+
+ err = s.d.Restart()
+ c.Assert(err, check.IsNil)
+
+ // both running
+ testRun(map[string]bool{"top1": true, "top2": true}, "After second daemon restart: ")
+
+}
+
func (s *DockerDaemonSuite) TestDaemonStartIptablesFalse(c *check.C) {
if err := s.d.Start("--iptables=false"); err != nil {
c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err)
diff --git a/man/docker-create.1.md b/man/docker-create.1.md
index fb70cabf4a..59179fe506 100644
--- a/man/docker-create.1.md
+++ b/man/docker-create.1.md
@@ -226,7 +226,7 @@ This value should always larger than **-m**, so you should always use this with
Mount the container's root filesystem as read only.
**--restart**="no"
- Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
+ Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped).
**--security-opt**=[]
Security Options
diff --git a/man/docker-run.1.md b/man/docker-run.1.md
index d48e412573..50a5013ee8 100644
--- a/man/docker-run.1.md
+++ b/man/docker-run.1.md
@@ -360,7 +360,7 @@ to write files anywhere. By specifying the `--read-only` flag the container wil
its root filesystem mounted as read only prohibiting any writes.
**--restart**="no"
- Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
+ Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped).
**--rm**=*true*|*false*
Automatically remove the container when it exits (incompatible with -d). The default is *false*.
diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go
index f6a89a314a..3266f1577f 100644
--- a/runconfig/hostconfig.go
+++ b/runconfig/hostconfig.go
@@ -140,6 +140,13 @@ func (rp *RestartPolicy) IsOnFailure() bool {
return rp.Name == "on-failure"
}
+// IsUnlessStopped indicates whether the container has the
+// "unless-stopped" restart policy. This means the container will
+// automatically restart unless user has put it to stopped state.
+func (rp *RestartPolicy) IsUnlessStopped() bool {
+ return rp.Name == "unless-stopped"
+}
+
// LogConfig represents the logging configuration of the container.
type LogConfig struct {
Type string
diff --git a/runconfig/parse.go b/runconfig/parse.go
index 5528d7af76..8c2e4060a3 100644
--- a/runconfig/parse.go
+++ b/runconfig/parse.go
@@ -450,9 +450,9 @@ func ParseRestartPolicy(policy string) (RestartPolicy, error) {
p.Name = name
switch name {
- case "always":
+ case "always", "unless-stopped":
if len(parts) > 1 {
- return p, fmt.Errorf("maximum restart count not valid with restart policy of \"always\"")
+ return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
}
case "no":
// do nothing