Docker Compose 停止容器:docker compose stop 命令详解与源码级原理
【免费下载链接】composeDefine and run multi-container applications with Docker项目地址: https://gitcode.com/GitHub_Trending/compose/compose
docker compose stop用于停止 Docker Compose 项目中正在运行的容器,但不会删除容器及其配置,已停止的容器可以随时通过docker compose start重新启动。它是 Compose 生命周期管理中最常用、最安全的“暂停式”操作之一,与down(停止并移除资源)、pause(冻结进程)等命令有本质区别。本文以 Docker Compose 官方参考文档 docs/reference/compose_stop.md 为骨架,结合命令实现(cmd/compose/stop.go)、服务端编排逻辑(pkg/compose/stop.go)以及端到端测试,完整解析其用法、参数、底层调用链与实战注意事项,帮助你精准掌控容器停止行为。
一、命令概览与选项说明
根据参考文档,docker compose stop的完整用法为:
docker compose stop [OPTIONS] [SERVICE...]命令的官方元数据(见 docs/reference/docker_compose_stop.yaml)定义如下:
| Name | Type | Default | Description(说明) |
|---|---|---|---|
--dry-run | bool | false | Execute command in dry run mode(演练模式,只打印将执行的动作而不真正停止) |
-t,--timeout | int | 0 | Specify a shutdown timeout in seconds(以秒为单位的停机超时时间) |
两个关键点:
SERVICE...是可选参数。不带任何服务名时,停止整个项目的所有服务容器;带上一个或多个服务名时,只停止指定服务,其依赖服务保持运行状态(下文第三节会从代码层面验证这一点)。--dry-run是全局持久化参数。从源码看,它并非在stop命令内部单独定义,而是在根命令上通过c.PersistentFlags().BoolVar(&dryRun, "dry-run", false, ...)注册(见 cmd/compose/compose.go),因此对up、down、restart、stop等命令通用。启用后,后端会被包装进演练模式(backendOptions.Add(compose.WithDryRun),见 cmd/compose/compose.go),Compose 会输出“将要做什么”而不真正对容器下发停止指令,适用于发布前核对操作范围。
stop子命令的 CLI 定义位于 cmd/compose/stop.go:
cmd := &cobra.Command{ Use: "stop [OPTIONS] [SERVICE...]", Short: "Stop services", PreRun: func(cmd *cobra.Command, args []string) { opts.timeChanged = cmd.Flags().Changed("timeout") }, RunE: Adapt(func(ctx context.Context, args []string) error { return runStop(ctx, dockerCli, backendOptions, opts, args) }), ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := cmd.Flags() flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")可以看到ValidArgsFunction: completeServiceNames(...)让 shell 可以对服务名做自动补全。而PreRun中通过cmd.Flags().Changed("timeout")记录用户是否显式传入过--timeout——这个细节对超时语义至关重要,见第三节。
二、核心语义:停止而不移除,随时可重启
参考文档对命令行为的定义非常简短但精确:
Stops running containers without removing them. They can be started again with
docker compose start.
即:stop只把运行中的容器置为exited(已退出)状态,容器对象本身、其文件系统层、网络配置和 Compose 项目标记都原样保留。这意味着:
- 不会销毁数据:容器内的写层数据不会被清除(
down的-v、rm的-v才会连带删除卷)。 - 可无缝恢复:直接执行 docker compose start 即可把同一批容器原样启动回来,无需重新
up、重建或重新分配端口,容器 ID 不变。 ps/ls可见性差异:停止后的项目在默认的docker compose ps/ls输出中不再显示为 running,需要使用--all才能看到 exited 状态的旧容器。
这一“同容器复活”的行为在端到端测试中有明确断言。在 pkg/e2e/start_stop_test.go 的TestStartStop中,测试依次执行:
Step("up starts every service", ComposeCmd("up", "-d"), ...) Step("stop halts the containers without removing them", ComposeCmd("stop"), ServiceState("simple", "exited"), ServiceState("another", "exited")) Step("a stopped project is hidden from ls", ...) Step("ls --all still lists the stopped project", ...) Step("start brings the same containers back", ComposeCmd("start"), ServiceState("simple", "running"), ServiceState("another", "running"), NotRecreated("simple", "another"))注意最后一步的NotRecreated("simple", "another")断言——它证明start唤醒的是原有的容器实例而非新建容器。对应的最小测试项目只有两个服务(pkg/e2e/fixtures/start-stop/compose.yaml):
services: simple: image: nginx:alpine another: image: nginx:alpine三、服务选择与停止顺序的源码级解析
stop命令最终通过后端backend.Stop(...)进入服务编排层。调用链如下:
- CLI 层
runStop(cmd/compose/stop.go)负责解析项目名并组装api.StopOptions; - API 层
composeService.Stop(pkg/compose/stop.go)通过Run(...)包装后在事件总线上广播一个名为"stop"的阶段事件; - 内部实现
stop(...)(pkg/compose/stop.go)真正执行停止逻辑。
api.StopOptions的结构定义在 pkg/api/api.go:
// StopOptions group options of the Stop API type StopOptions struct { // Project is the compose project used to define this app. Might be nil if user ran command just with project name Project *types.Project // Timeout override container stop timeout Timeout *time.Duration // Services passed in the command line to be stopped Services []string }结合 pkg/compose/stop.go 的实现,可以提炼出几个关键行为规则:
规则 1:默认停止整个项目,可精确到指定服务。当没有传入任何服务名时,代码用project.ServiceNames()补全全部服务;而当只指定部分服务时,其余服务不受影响:
if len(options.Services) == 0 { options.Services = project.ServiceNames() }规则 2:按“依赖逆序”停止,且允许指定服务跳过遍历。停止通过InReverseDependencyOrder遍历依赖图,先停被依赖方、再停依赖方,避免服务在停止过程中请求已经不可用的上游:
return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error { if !slices.Contains(options.Services, service) { return nil } ... })TestStartStopWithDependencies(pkg/e2e/start_stop_test.go)用“foo 依赖 bar”的项目验证了:docker compose stop foo只会停掉 foo、bar 保持 running;随后docker compose start foo会把依赖 bar 一并启动回来。
规则 3:one-off(一次性/手动 run)容器不受影响。在拉取容器列表时使用了oneOffExclude过滤,仅处理由 Compose 管理、带com.docker.compose服务标签的常规服务容器。TestStartStopWithOneOffs(pkg/e2e/start_stop_test.go)专门验证了docker compose run -d产生的一次性容器在stop/start/restart前后始终保持运行、不被误伤。
规则 4:由插件(Provider)管理的服务交给插件处理。对于设置了Provider的服务(扩展型服务),不会直接调用 Docker API,而是走runPlugin(ctx, project, serv, "stop")把停止操作委托给对应的插件实现。
四、单容器停止与超时(-t / --timeout)机制
最终对每个容器的停止动作在 pkg/compose/down.go 的stopContainer中完成:
func (s *composeService) stopContainer(...) error { eventName := getContainerProgressName(ctr) s.events.On(newEvent(eventName, api.Working, api.StatusStopping)) if service != nil { for _, hook := range service.PreStop { err := s.runHook(ctx, ctr, *service, hook, listener) ... } } _, err := s.apiClient().ContainerStop(ctx, ctr.ID, client.ContainerStopOptions{ Timeout: utils.DurationSecondToInt(timeout), }) ... s.events.On(newEvent(eventName, api.Done, api.StatusStopped)) return nil }该函数的执行细节揭示了三个与参考文档对应的深层机制:
- 进度事件:停止前后会发出
Stopping(Working)与Stopped(Done)状态事件,这是docker compose stop输出进度条/状态列表的数据来源。 - PreStop 钩子:若服务的 Compose 模型配置了
x-扩展字段定义的 PreStop hooks(相关测试见TestPreStopHookSuccess等 fixture),会在真正调用 Docker 停止 API 之前依次执行;若钩子执行时容器已经不存在或已处于停止冲突状态(NotFound/Conflict),该错误会被吞掉并直接返回。 - 超时透传:用户传入的
--timeout秒数被换算成*time.Duration,再通过ContainerStop交给容器引擎。
这里需要特别解释--timeout的“默认 0”到底意味着什么。回到 CLI 层 cmd/compose/backend.go 的工具函数:
// optionalTimeout converts an integer timeout (in seconds) into a *time.Duration. // If changed is false, nil is returned (no timeout was explicitly set). func optionalTimeout(t int, changed bool) *time.Duration { if !changed { return nil } d := time.Duration(t) * time.Second return &d }只有当用户在命令行中显式写入了-t/--timeout时(PreRun阶段记录的timeChanged为true),超时值才会被封装为*time.Duration传入StopOptions.Timeout;否则传nil,即不覆盖任何默认值,交由容器引擎自身的停机超时策略决定。因此:
docker compose stop(不传-t):使用引擎默认的停止宽限期,容器收到SIGTERM后若在宽限期内未自行退出才被强制SIGKILL,是优雅停机的推荐姿势;docker compose stop -t 30:给容器 30 秒的优雅退出时间窗口;docker compose stop -t 0:不等待优雅退出,立即强制终止。
同时,对同一个服务下的多个副本容器,停止是并行执行的——stopContainers(pkg/compose/down.go)利用errgroup并发下发stopContainer,因此副本数多时不会串行拖慢整体停机速度。
五、实战场景与常用组合
基于上面的事实,docker compose stop在以下几类场景中最有价值:
场景 A:临时下线项目、保留现场需要暂停服务但马上又要恢复(如维护窗口、夜间节能),用 stop + start 即可“原封不动”地回来:
# 停掉整个项目(容器保留,退出码路径不变) docker compose stop # 稍后原容器恢复运行 docker compose start场景 B:只停部分服务,保留依赖运行对某个无状态服务做灰度下线,其上游 DB 保持在线:
docker compose stop web docker compose ps # 观察 web=exited,db 仍 running docker compose start web注意与stop配套的语义边界:docker compose restart(docs/reference/compose_restart.md)会先停再启、适合快速生效配置变更;而docker compose pause(docs/reference/compose_pause.md)使用SIGSTOP冻结进程、容器状态仍显示为 running,适合临时挂起;down/rm则直接移除容器,属于破坏性操作。若不确定命令是否会误删资源,可以先加--dry-run演练:
docker compose --dry-run stop该参数会在不触碰真实容器的前提下,把本次stop将要执行的操作打印出来供核对。
场景 C:脚本化、CI 中的幂等收尾由于停止不改变项目资源结构,在 CI 中反复stop/start不会产生资源泄漏,也不会像down那样需要重新走一次网络/卷的创建流程,收敛速度快、副作用小。停止后如需彻底清理,再显式执行docker compose down。
六、相关文档与源码索引
- 命令参考:本文主体 docs/reference/compose_stop.md,配套元数据 docs/reference/docker_compose_stop.yaml
- 反向命令:docker compose start、docker compose restart、docker compose pause
- CLI 入口与参数绑定:cmd/compose/stop.go,持久化
--dry-run注册于 cmd/compose/compose.go - 后端编排:pkg/compose/stop.go(依赖逆序、插件代理、one-off 过滤)
- 容器级实现:
stopContainer/stopContainers(pkg/compose/down.go) - API 选项结构:
StopOptions(pkg/api/api.go) - 行为验收测试:pkg/e2e/start_stop_test.go(含
TestStartStop、TestStartStopWithDependencies、TestStartStopWithOneOffs、TestStartStopMultipleServices等),fixture 见 pkg/e2e/fixtures/start-stop/compose.yaml
七、小结
docker compose stop的核心价值在于“停而不删、启而原样”:容器进入 exited 状态但实例、数据、网络归属全部保留,配合start可实现无损的上下线循环。把握三个要点即可用好该命令:不带服务名=停整个项目、-t不显式传入时沿用引擎默认优雅停机窗口、one-off 与依赖服务遵循独立规则不受连带影响。如果需要在不产生任何副作用的前提下确认操作范围,别忘了--dry-run这个全局演练开关。
【免费下载链接】composeDefine and run multi-container applications with Docker项目地址: https://gitcode.com/GitHub_Trending/compose/compose
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考