Docker Compose CLI 中 watch 命令深度解析:从 alpha watch 参数到 develop.watch 热重载实现
docker compose alpha watch 是 Compose 项目(Define and run multi-container applications with Docker)提供的文件监视命令参考页:监视服务的构建上下文,在文件更新时自动重建/刷新容器。本文以该参考文档为主线,完整覆盖其命令用法与全部参数,并结合 cmd/compose/watch.go 与 pkg/compose/watch.go 的源码实现,讲清 develop.watch 配置、事件处理与同步/重建流水线,读完后可直接在本地开发流程中落地「改代码即生效」的容器化工作流。
命令定位:alpha watch 已成为顶级命令
参考文档 docs/reference/compose_alpha_watch.md 记录的是实验性子命令入口:
usage: docker compose alpha watch [SERVICE...]
Watch build context for service and rebuild/refresh containers when files are updated
需要注意的是,在当前仓库中 watch 已从实验性命令"转正"为顶级命令,alpha 只是历史入口。源码中有两处直接证据:
- 在 cmd/compose/compose.go,
watchCommand与alphaCommand被并列注册为顶级子命令; - 在 cmd/compose/watch.go 的
RunE中,如果检测到命令是通过alpha父命令调用的,会打印一条警告:
if cmd.Parent().Name() == "alpha" {
logrus.Warn("watch command is now available as a top level command")
}
也就是说,执行 docker compose alpha watch 依然可用,但会提示用户改用 docker compose watch。当前 cmd/compose/alpha.go 中的 alpha 子命令组只保留了 viz、publish、generate 三个实验性命令,watch 不再属于其中。顶级入口的参考文档见 docs/reference/compose_watch.md。
完整参数说明
参考文档列出的选项如下(--dry-run 为从项目选项继承而来的选项):
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--dry-run |
bool | Execute command in dry run mode | |
--no-up |
bool | Do not build & start services before watching | |
--quiet |
bool | hide build output |
从 cmd/compose/watch.go 的 flag 注册代码看,还存在一个文档表中未体现的选项 --prune,且在顶级命令中默认开启:
cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
cmd.Flags().BoolVar(&watchOpts.prune, "prune", true, "Prune dangling images on rebuild")
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
逐项解释:
--no-up(默认false):默认行为是 watch 启动时先执行一次等价于up的流程——构建、创建并启动服务,然后才进入监视循环;加上该参数则跳过这步,假定服务已经在运行,直接进入文件监视。--quiet(默认false):隐藏构建输出。它同时作用于初始up阶段的拉取(QuietPull)与后续 rebuild 的构建日志(见 cmd/compose/watch.go)。--dry-run:继承自项目级选项,以干跑模式执行命令。--prune(默认true):每次重建后清理属于本项目的 dangling 镜像,避免反复 rebuild 导致本地磁盘堆积旧镜像。实现位于 pkg/compose/watch.go 的pruneDanglingImagesOnRebuild:按项目名过滤出dangling=true的镜像,再排除本次新构建出的镜像 ID,其余全部ImageRemove。
命令的参数是可选的服务名列表 [SERVICE...],并支持按服务名自动补全(ValidArgsFunction: completeServiceNames)。
前置条件:develop.watch 配置
watch 命令本身不产生监视规则,规则来自 Compose 文件中的 develop 段(Compose Specification 的 develop.watch 特性)。典型配置如下(取自端到端测试夹具 pkg/e2e/fixtures/watch/compose.yaml):
x-dev: &x-dev
watch:
- action: sync
path: ./data
target: /app/data
ignore:
- '*.foo'
- ./ignored
- action: sync+restart
path: ./config
target: /app/config
services:
alpine:
build:
dockerfile_inline: |-
FROM alpine
RUN mkdir -p /app/data
RUN mkdir -p /app/config
init: true
command: sleep infinity
develop: *x-dev
各字段与行为对应源码中的处理:
path:宿主机上要监视的路径。相对路径会在加载时解析为基于项目工作目录的绝对路径,并做符号链接展开与Clean,见 pkg/compose/watch.go 中loadDevelopmentConfig的处理。target:容器内对应的目标路径。当设置target时,宿主机相对路径会被转换成 Unix 风格的容器内相对路径(见 pkg/compose/watch.go 中watchRule.Matches的filepath.Rel+path.Join逻辑),sync 动作据此把文件写入容器的正确位置。action:触发动作,共 5 种,全部在 pkg/compose/watch.go 的handleWatchBatch中按类型分派:rebuild:重建服务镜像并强制重建(recreate)容器。要求服务必须有build上下文,否则在加载阶段就报错can't watch service ... with action rebuild without a build context(见 cmd/compose/watch.go 与 pkg/compose/watch.go)。sync:把变更文件同步进正在运行的容器,不重启。sync+restart:先同步文件,再重启服务容器(测试夹具中用于配置文件热更新场景)。sync+exec:同步文件后在容器内执行一段exec.command命令(多容器服务会在每个容器上并行执行,见 pkg/compose/watch.go)。restart:只重启,不同步文件。
include/ignore:glob 过滤。实现上是一个组合匹配器(composite matcher),把.dockerignore(通过watch.LoadDockerIgnore(service.Build)从构建上下文加载)、临时文件匹配器、硬编码的.git/忽略规则、用户ignore模式叠加在一起,见 pkg/compose/watch.go 的getWatchRules。没有配置include时默认使用AnyMatcher放行全部文件。initial_sync:声明该 sync 规则在监视启动时先做一次全量初始同步,实现见 pkg/compose/watch.go 的initialSync。注意旧的扩展属性x-initialSync已被标记为弃用(deprecated),使用它会打印警告并提示改用官方的initial_sync属性(见 pkg/compose/watch.go)。x-develop:develop的旧写法,同样已弃用,加载时会打印x-develop is DEPRECATED警告(见 pkg/compose/watch.go)。
rebuild 动作还有一个隐含约束:prepareRebuildTriggers(pkg/compose/watch.go)会把含 rebuild 触发器的服务的拉取策略设为 PullPolicyBuild,即永远构建而不是拉取远程镜像。
执行流程:从 runWatch 到监视循环
1. 入口与项目锁
docker compose alpha watch [SERVICE...] 的完整流程入口是 cmd/compose/watch.go 的 runWatch,依次做四件事:
- 加载项目(
WithoutEnvironmentResolution),再仅针对选中的服务解析环境变量——这样无关服务声明的env_file不存在也不会导致失败; - 通过 internal/locker 的 pidfile 对项目加互斥锁(
locker.NewPidfile(project.Name)+Lock()),防止同一项目被多个 compose 进程并发操作; - 若未指定
--no-up,执行一次Up(RecreateDiverged、Inherit: true、RemoveOrphans: false),把服务先拉起来; - 调用
backend.Watch进入监视循环,日志通过formatter.NewLogConsumer输出到终端。
2. 收集触发路径并启动监视
核心实现在 pkg/compose/watch.go 的 composeService.watch:
- 遍历每个服务,解析
develop(或弃用的x-develop)配置,没有配置develop的服务直接跳过;若最终没有任何触发路径,会报错none of the selected services is configured for watch, consider setting a 'develop' section; watchTriggerPaths(pkg/compose/watch.go)有两个关键判断:- 如果 sync 规则的
path已经被该服务的 bind mount 卷覆盖,则跳过监视并告警:path '%s' also declared by a bind mount volume, this path won't be monitored!——因为 bind mount 本身就会实时共享,无需再同步; - 如果规则声明了初始同步,则在启动监视前先执行
initialSync。
- 如果 sync 规则的
- 文件事件来自 pkg/watch 的
watch.NewWatcher(paths),跨平台实现分别在watcher_nonwin.go/watcher_windows.go/watcher_darwin.go。
3. 事件批处理与动作执行
watchEvents(pkg/compose/watch.go)是一个长期运行的 select 循环:
- 使用
watch.BatchDebounceEvents对文件系统事件做去抖与批处理,把 IDE 一次性保存大量文件识别为单个 batch,避免一次保存触发几十次构建; - 单批超过 1000 个文件时会打印警告,并提示"如果刚切换了分支,这是预期行为";
- 每个 batch 交给
handleWatchBatch(pkg/compose/watch.go)分派:按规则匹配事件路径后,聚合出rebuild/syncfiles/restart/exec四类动作,执行顺序固定为 先 rebuild → 再 sync → 再 restart → 最后并行 exec。
4. sync:tar 打包 + CopyToContainer 传输
sync 的具体传输实现由 getSyncImplementation 决定(pkg/compose/watch.go):当前唯一可用实现是 tar 批量传输(可用环境变量 COMPOSE_EXPERIMENTAL_WATCH_TAR=false 关闭,关闭后直接报 no available sync implementation)。其原理是把变更文件打包成 tar 归档,通过 Docker Engine 的 CopyToContainer API(DestinationPath: "/"、CopyUIDGID: true)批量写入容器,见 pkg/compose/watch.go;归档打包逻辑在 internal/sync/tar.go。
5. rebuild:只重建被监视的服务
rebuild 路径(pkg/compose/watch.go)值得注意两点:
- 不级联依赖:
up --build为了初始启动会构建depends_on的依赖镜像(Deps=true),而 watch 触发重建时会显式把buildOpts.Deps重置为false、Services限定为被改动的服务——否则改一个服务会级联重建整条依赖链。端到端测试 TestWatchRebuildIgnoresDependencies 专门验证了"改 frontend 不会触发 backend 重建"; - 重建闭环:build → (可选)清理 dangling 镜像 →
RecreateForce强制创建 →start启动并附加日志。
典型用法示例
结合 e2e 测试(pkg/e2e/watch_test.go)中的真实调用方式,常见用法有三类:
1. 直接启动监视(默认先 up 再 watch)
docker compose alpha watch web # 历史写法,会提示已转正
docker compose watch web # 推荐写法
对应夹具 pkg/e2e/fixtures/watch/rebuild.yaml:三个服务 a/b/c 都用 path: test, action: rebuild 监视同一个文件,修改该文件后三个服务会依次重建并生效。
2. 与 up 组合使用
docker compose up --watch
docker compose up --build --watch
e2e 测试中 up --watch 是主要验证入口,--build 用于确保带 build 上下文的镜像在启动与重建时都本地构建。
3. 服务已运行时只开监视
docker compose up -d
docker compose watch --no-up web
--no-up 跳过初始构建与启动,适合服务已由其他流程拉起、只需增量同步文件的场景;配合 --quiet 可压掉构建输出,--prune=false 可在需要保留历史镜像时关闭 dangling 清理。
端到端测试验证的行为边界
pkg/e2e/watch_test.go 中的测试用例界定了该命令的行为边界,可作为验收清单:
- 文件修改/删除/子目录增删:
TestWatch覆盖改内容、删文件、建子目录、删目录等场景,确认容器内状态与宿主机一致(pkg/e2e/watch_test.go); - ignore 生效:写入
data.foo(匹配*.foo)与ignored/目录下的文件不会同步进容器; - sync+restart:修改
./config下文件后,输出中出现service(s) [...] restarted且新内容可读(pkg/e2e/watch_test.go); - sync+exec:
TestWatchExec使用 fixtures/watch/exec.yaml,在文件变更后容器内执行命令并输出结果; - rebuild 不级联依赖:
TestWatchRebuildIgnoresDependencies; - 符号链接目录:
TestWatchSyncIntoSymlinkedDirectory验证同步会穿透镜像内预置的符号链接,而不是覆盖它; - include 过滤:
TestWatchIncludes验证只有A.test被同步、B.test被过滤。
小结
docker compose alpha watch 参考页描述的命令在当前仓库中已是顶级命令 docker compose watch,参数为 --dry-run、--no-up、--quiet,源码中另有默认开启的 --prune 用于重建后清理 dangling 镜像。其本质是:基于 Compose 文件中的 develop.watch 规则(rebuild / sync / sync+restart / sync+exec / restart 五种动作),对触发路径做跨平台文件监视,事件经去抖批处理后,通过 tar + CopyToContainer 同步文件或触发"构建—重建—启动"闭环,从而实现容器化开发环境下的实时热重载。相关入口与实现可进一步查看 cmd/compose/watch.go、pkg/compose/watch.go 与 pkg/e2e/fixtures/watch/compose.yaml。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00