Docker 镜像升级是日常运维里最烦又最不能不做的事——CVE 漏洞、版本过期、安全审计都要求镜像保持最新。Watchtower 把这件"每月手动 docker pull + restart"的事自动化:它会按周期拉取镜像、检查新版本、自动重启容器。
阅读对象:管 5+ 容器实例的运维 / DevOps
覆盖范围:Watchtower 基础用法 + 选择性更新 + 标签筛选 + 远程仓库认证 + 远程主机升级 + 定时调度 + 监控日志
一、为什么需要 Watchtower
手动升级 Docker 容器的痛点:
- CVE 爆出 → 需要紧急升级 nginx → 10 台机器挨个 pull + restart
- 测试环境新版本发布 → 手动改 5 个 compose 文件
- 半夜收到 Prometheus 告警 → 紧急 docker pull redis:7.2-alpine
Watchtower 本质:把"定期升级"做成可调度、可过滤、可观测的 cron 任务。
When to use:
- 测试 / 预发环境镜像更新(强烈推荐)
- 内部工具的镜像(推荐)
- 生产环境(谨慎使用,建议灰度 + 人工 confirm)
不适用:需要特殊启动参数 / 一次性初始化(如数据库迁移)的镜像——升级可能丢数据。
二、最小化启动
1
2
3
4
5
6
| docker run -d \
--name watchtower \
--restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
-c
|
参数说明:
-v /var/run/docker.sock:必须挂载,Watchtower 通过 Docker API 操作其他容器-c / --cleanup:升级后自动删除旧镜像——避免磁盘满
启动后 Watchtower 默认 每 5 分钟轮询一次所有运行容器的镜像,看是否有新版本。
三、选择性更新
只升级特定容器,避免 Watchtower 动到不该动的:
1
2
3
4
5
6
7
| docker run -d \
--name watchtower \
--restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
-c \
nginx redis mysql
|
nginx redis mysql 是容器名列表——只升级这几个,其他的不动。
3.1 通过文件列表更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 创建列表
cat > ~/.watchtower.list <<'EOF'
aria2-pro
unlockmusic
mtg
nginx
EOF
# 2. 启动
docker run -d \
--name watchtower \
--restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /root/.watchtower.list:/root/.watchtower.list:ro \
containrrr/watchtower \
-c \
$(cat ~/.watchtower.list)
|
好处:增删容器名只改文件,不用重启 Watchtower。
四、排除特定容器
给容器加 label com.centurylinklabs.watchtower.enable=false,Watchtower 会自动跳过:
1
2
3
4
5
6
7
8
| docker run -d \
--name openwrt-mini \
--restart always \
--network openwrt \
--privileged \
--label com.centurylinklabs.watchtower.enable=false \
p3terx/openwrt-mini \
/sbin/init
|
反向操作:watchtower 只升级显式标记的容器,其他一律不管。
启动 Watchtower 时加 --label-enable:
1
2
3
4
5
6
| docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -c \
--label-enable
|
启动容器时加 --label com.centurylinklabs.watchtower.enable=true:
1
2
3
4
5
| docker run -d \
--name my-critical-app \
--restart always \
--label com.centurylinklabs.watchtower.enable=true \
myapp:latest
|
五、调度频率
5.1 间隔模式
1
2
3
4
5
6
7
| # 每 3600 秒(1 小时)检查一次
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -c \
--interval 3600
|
5.2 Cron 模式
1
2
3
4
5
6
7
| # 每天凌晨 2 点检查
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -c \
--schedule "0 0 2 * * *"
|
注意:Watchtower 的 cron 是 6 位(秒 分 时 日 月 周),比传统 cron 多 1 位秒。
六、私有 Registry 认证
公司用 Harbor 时,Watchtower 升级私有镜像需要认证:
1
2
3
4
5
6
7
8
9
| docker run -d \
--name watchtower \
--restart always \
-e REPO_USER=admin \
-e REPO_PASS={{REDACTED}} \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
-c \
base
|
环境变量 REPO_USER / REPO_PASS 会被 Watchtower 用于 docker login Harbor。
更安全的做法:用 ~/.docker/config.json 挂载:
1
2
3
4
5
6
7
8
| docker run -d \
--name watchtower \
--restart always \
-v /root/.docker/config.json:/config.json \
-e REPO_USER=$(jq -r .auths."dockerhub.example.com".auth /config.json | base64 -d | cut -d: -f1) \
-e REPO_PASS=$(jq -r .auths."dockerhub.example.com".auth /config.json | base64 -d | cut -d: -f2) \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
|
七、远程主机升级
通过 DOCKER_HOST 环境变量,让本机 Watchtower 升级远程机器的容器:
1
2
3
4
5
6
7
8
9
10
| docker run -d \
--name watchtower \
--restart always \
-e REPO_USER=admin \
-e REPO_PASS={{REDACTED}} \
-e DOCKER_HOST="tcp://203.0.113.10:2375" \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
-c \
base
|
前提:远程机器的 Docker daemon 监听 0.0.0.0:2375。
生产强烈推荐 TLS(tcp://host:2376 + CA 证书),避免 2375 明文端口被扫到。
八、手动触发更新
偶尔需要立即升级某容器(不等调度),用 --run-once:
1
2
| docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -cR aria2-pro
|
参数:
-c:清理旧镜像-R / --run-once:跑一次就退出,不进入常驻守护模式
适用场景:
- CVE 紧急升级
- 调试时反复升级测
- 一次性升级指定容器
九、监控与日志
9.1 看实时日志
1
| docker logs -f watchtower
|
输出示例:
1
2
3
4
| time="2024-06-15T03:00:00Z" level=info msg="Found new redis:7.2-alpine image"
time="2024-06-15T03:00:01Z" level=info msg="Stopping /running redis (abc123)"
time="2024-06-15T03:00:05Z" level=info msg="Creating /running redis (def456)"
time="2024-06-15T03:00:10Z" level=info msg="Updated container redis"
|
9.2 接入 Loki / ELK
1
2
3
4
5
6
7
8
9
| docker run -d \
--name watchtower \
--restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
--log-driver=loki \
--log-opt loki-url="http://internal.example.com:3100/loki/api/v1/push" \
--log-opt max-size="50m" \
--log-opt max-file="10" \
containrrr/watchtower
|
告警规则(Loki + Grafana):
1
2
| # 升级失败告警
{job="watchtower"} |= "level=error"
|
9.3 Prometheus 指标(通过 cAdvisor)
Watchtower 自身不暴露 metrics 端点,但它操作的容器被 cAdvisor 抓取——可以看每个容器重启次数。
十、典型坑位
10.1 Watchtower 自己被升级
症状:Watchtower 升级自己时失败,陷入死循环。
修复:给 Watchtower 加 disable label:
1
2
3
4
5
6
7
| docker run -d \
--name watchtower \
--restart always \
--label com.centurylinklabs.watchtower.enable=false \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
-c
|
或者用 --label-enable 模式 + 给所有其他容器显式 enable。
10.2 升级后容器启动失败
症状:Watchtower 升级 redis:7.2 → 7.4,但 7.4 改了配置文件路径,容器启动失败。
修复:升级前在测试环境验证;或者用 --no-restart 模式只下载不重启:
1
2
3
4
5
| docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--no-restart
|
人工 确认升级后的容器能起来,再 docker restart。
10.3 数据库容器被升级丢数据
症状:MySQL 8.0.36 → 8.4 自动升级,数据文件结构不兼容,启动报错。
修复:永远别让 Watchtower 自动升级数据库——给 mysql / postgres / mongodb 容器加 disable label。
10.4 大量容器同时升级
症状:20 个容器同时重启,业务抖动 30 秒。
修复:分批升级——用容器名列表分多个 Watchtower:
1
2
3
4
5
6
7
| # Watchtower 1:升级 nginx / redis
docker run -d --name wt-frontend -v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -c nginx redis --schedule "0 0 2 * * *"
# Watchtower 2:升级 backend
docker run -d --name wt-backend -v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -c api worker scheduler --schedule "0 30 2 * * *"
|
十一、生产级建议
11.1 灰度升级
1
2
3
4
5
6
7
8
| # 1. 升级 1 台机器(canary)
DOCKER_HOST=tcp://host-1:2375 watchtower -cR --run-once api
# 2. 监控 5 分钟无异常
# 3. 批量升级其他机器
for host in host-2 host-3 host-4; do
DOCKER_HOST=tcp://$host:2375 watchtower -cR --run-once api
done
|
11.2 升级前快照
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 升级前 commit 当前容器为镜像
docker commit api api:pre-upgrade-backup
# 2. 触发升级
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower -cR api
# 3. 监控 10 分钟
# 4. 出问题回滚
docker stop api
docker rm api
docker run -d --name api api:pre-upgrade-backup
|
11.3 通知集成
Slack 通知(用 [shouts](https://github.com/containrrr/shoutrrr) 库,Watchtower 内置支持):
1
2
3
4
5
6
| docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \
-e WATCHTOWER_NOTIFICATION_URL=slack://token@channel \
containrrr/watchtower
|
支持的通知方式:Slack / Discord / Telegram / Email / Pushover / Gotify / Microsoft Teams / Mattermost / Smtp。
十二、完整生产配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| version: "3.8"
services:
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=Asia/Shanghai
- WATCHTOWER_LABEL_ENABLE=true # 只升级标记的容器
- WATCHTOWER_INCLUDE_STOPPED=false
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 3 * * * # 每天凌晨 3 点
- WATCHTOWER_TIMEOUT=60s
- REPO_USER=admin
- REPO_PASS={{REDACTED}}
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_URL=slack://{{SLACK_TOKEN}}@{{SLACK_CHANNEL}}
labels:
- "com.centurylinklabs.watchtower.enable=false" # 自身不升级
|
业务容器(要被自动升级的):
1
2
3
4
5
| services:
nginx:
image: nginx:1.25-alpine
labels:
- "com.centurylinklabs.watchtower.enable=true"
|
十三、Watchtower 替代方案
| 工具 | 优势 | 劣势 |
|---|
| Watchtower | 简单、单二进制 | 不支持 K8s |
| Diun(diun) | 只通知不升级,安全首选 | 不自动操作 |
| Renovate / Dependabot | 适合 K8s manifest 自动 PR | 需要 GitOps 体系 |
| ArgoCD Image Updater | K8s 生态完整 | K8s only |
K8s 用户推荐 ArgoCD Image Updater + Renovate 组合——Watchtower 在 K8s 里不是最佳实践。
十四、安全加固清单
/var/run/docker.sock 权限最小化:默认是 root 拥有,容器挂载后相当于 root in container- 加 label
disable:防止 Watchtower 升级自己 - 数据库容器 disable:避免自动升级导致数据丢失
- 审计日志:所有 Watchtower 操作都进 Loki,定期回看
- CVE 优先级:用
Diun + Watchtower 组合,先通知再决定升不升
2024+ 视角补充
本文写于 2024-06,2024-2026 期间容器自动更新工具演进:
- Watchtower 1.7+(2024):Podman 原生支持(除 Docker 外);OCI 1.1+ 镜像清单;HTTP API(查询 / 触发 / 状态);Healthcheck endpoint
- Diun 4.x(2024-2025):仍是 “只通知不升级” 首选——配合 Watchtower 是稳态组合
- Renovate / Dependabot 2024+:K8s manifest 自动 PR 仍是 GitOps 体系推荐
- ArgoCD Image Updater 0.13+:K8s 自动化 + GitOps 黄金组合
- K8s 场景新选择:Keel.sh(轻量 Deployment 自动滚动);Pulumi + Kubernetes Operator
- OrbStack / Docker Desktop 5.x:本地开发环境已经内置自动更新检测
实战建议(2025-2026 视角):
- Docker 单机 / VM → Watchtower + Diun 仍是首选
- K8s → ArgoCD Image Updater + Renovate 黄金组合
- 企业合规 → Diun 通知 + 人工审核 + 灰度(不要让 Watchtower 自动升级生产数据库)
- 新趋势:AI 辅助 CVE 评估(如 Snyk / Aikido)——自动判断镜像升级是否引入 breaking change
下一步