上篇写的 autodok 是「按需触发」派:CI 构建完镜像后 POST 一下,它才去更新容器。这篇写它的对立面——轮询派的代表 watchtower:自己定时去问镜像仓库「有没有新镜像」,有就拉下来原地重启容器,全程不需要 CI 参与。
两种方式没有优劣,只有适不适合,文末给出选型对比。
项目地址
- GitHub:containrrr/watchtower(Go 编写,Apache-2.0 协议)
- 官方文档:containrrr.dev/watchtower
- Docker Hub:containrrr/watchtower
工作原理一句话:watchtower 挂载 docker.sock,按设定的周期拿着运行中容器的镜像去仓库比对 digest,发现新镜像就拉取,然后用原来的启动配置(卷、端口、环境变量)重建容器。
部署方式
最简一行跑起来:
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
实际使用推荐 compose,把常用参数写清楚:
services:
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 3600 --cleanup --rolling-restart
--cleanup 让更新后顺手删掉旧镜像,避免磁盘被旧镜像悄悄吃满;--interval 3600 把默认 24 小时的轮询周期改成 1 小时。
常用配置速查
| 参数 | 作用 |
|---|---|
--interval 3600 | 轮询周期(秒),默认 86400(24 小时) |
--schedule "0 0 4 * * *" | 用 cron 表达式定时(六段式,含秒),与 interval 二选一 |
--cleanup | 更新后删除旧镜像 |
--rolling-restart | 多副本时逐个重启,避免同时断 |
--monitor-only | 只检测不更新,有更新时发通知(生产建议先用这个观察一阵) |
--include-stopped / --include-restarting | 把停止/重启中的容器也纳入管理 |
--remove-anonymous-volumes | 更新时连带删除匿名卷(注意数据安全) |
--run-once | 只跑一轮就退出,适合手动执行或定时任务 |
--label-enable | 只管理带 com.centurylinklabs.watchtower.enable=true 标签的容器 |
只更新指定容器
默认管理所有容器,生产上一般要收窄范围。两种姿势:
命令行点名:直接在命令末尾写容器名,只管理列出的:
docker run -d --name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower myapp nginx --interval 3600
标签过滤(推荐,配合 --label-enable):只动显式打标的容器,新增容器默认不受管,最安全:
services:
myapp:
image: registry.example.com/myapp:latest
labels:
- com.centurylinklabs.watchtower.enable=true
反过来,给容器打 com.centurylinklabs.watchtower.enable=false 可以明确排除。
更新通知
更新动作发生在凌晨四点而你毫不知情,出了问题就是事故。watchtower 内置 shoutrrr 通知,配置一个 URL 即可,支持 Slack、Discord、Gotify、邮件 SMTP、Microsoft Teams、Telegram、ntfy 等几十种渠道,多个地址空格分隔:
environment:
- WATCHTOWER_NOTIFICATION_URL=slack://watchtower@token-a/token-b/token-c gotify://gotify.example.com/token
配合 --notification-title-tag 加标题前缀,多台机器跑多个 watchtower 时能分清通知来自哪台。
HTTP API 模式:也能被 CI 触发
很多人不知道 watchtower 也有按需触发的一面。开启 HTTP API 模式后,它暴露 POST /v1/update 端点,外部(比如 CI 流水线)可以主动触发更新:
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --http-api-update
environment:
- WATCHTOWER_HTTP_API_TOKEN=mytoken
ports:
- 8080:8080
# 触发所有受管容器的更新
curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update
# 只更新指定镜像
curl -H "Authorization: Bearer mytoken" "localhost:8080/v1/update?image=foo/bar,foo/baz"
注意:开了 API 模式后默认关闭周期轮询,想两者并存要加 --http-api-periodic-polls。这个设计思路和 autodok 一致——把触发权交还给流水线。
私有镜像仓库
拉私有仓库的镜像需要凭据,两种方式:
# 方式一:环境变量
-e REPO_USER=username -e REPO_PASS=password
# 方式二:挂载 docker login 后的 config.json(推荐)
-v $HOME/.docker/config.json:/config.json
方式二有个坑:bind mount 绑定的是 inode,docker login 或编辑器保存文件时是「新建再替换」,inode 变了,容器里的凭据就停留在旧版本。解决办法是挂软链接——先 ln -s ~/.docker/config.json ~/docker-config.json,再挂载软链接,inode 就稳定了。
实践中的几个坑
- 生产慎用全自动:数据库、有状态服务不要交给 watchtower 自动更新,镜像一换数据面就重建,滚动更新和备份策略都不是它的职责。这类服务打
enable=false排除掉 - 先 monitor-only 观察:上线初用
--monitor-only+ 通知跑一两周,确认更新频率和对象符合预期再开自动更新 - cleanup 是把双刃剑:删旧镜像省磁盘,但出问题时少了「秒回滚旧镜像」这条路。重要服务可以不开 cleanup,改靠定期
docker image prune - 生命周期钩子:需要在更新前执行备份/排空流量等操作,可以看官方文档的 lifecycle hooks,给容器配
pre-update/post-update命令
watchtower vs autodok 选型
| 维度 | watchtower(轮询) | autodok(按需) |
|---|---|---|
| 触发方式 | 自己定期查仓库 | 等 CI POST /update |
| 更新时机 | 有延迟(取决于轮询周期) | 精确到「这次构建」 |
| CI 依赖 | 无,仓库里有新镜像就动 | 需要流水线里加一步 |
| 通知能力 | 内置 shoutrrr 多渠道 | 无(看 CI 自己的通知) |
| 适用场景 | 个人服务、第三方镜像跟进、无 CI 的小项目 | 有完整 CI/CD、要求更新时机精确的环境 |
两者不互斥:我自己的用法是生产用 autodok 跟着流水线走,个人小服务用 watchtower 开着 label 过滤自动跟进。
小结
watchtower 的价值在于「零接入成本」:挂个 socket、起个容器,存量服务立刻获得自动更新能力。代价是更新时机的控制权不在你手里。用 label 过滤收窄管理范围、配上通知渠道、重要服务坚决排除,它就是一个省心的守夜人。