起因
NAS 上的容器不知不觉攒到了 10 个:MoviePilot、Emby、Plex、Navidrome、qBittorrent、Transmission、Vaultwarden、IYUUPlus、cloudflared、mihomo。用是都好用,但一查镜像日期吓一跳:
cloudflare/cloudflared:latest 7 months ago
emby/embyserver:latest 7 months ago
linuxserver/plex:latest 7 months ago
deluan/navidrome:latest 4 months ago
vaultwarden/server:latest 2 months ago
媒体服务器这种东西七个月不更,安全补丁全欠着。docker system df 再一看,还有 6.6GB 的孤儿卷和 900MB 构建缓存躺着吃硬盘。
问题拆开是四件事:
- Container Manager 拉镜像时灵时不灵(国内镜像加速站不稳定);
- 一堆容器该更新了;
- 垃圾没人清;
- 以后怎么知道有更新——总不能定期手动挨个查。
思路:通知我,但别替我动手
先说最关键的决策:不用 Watchtower 全自动更新。
自动更新在家用 NAS 场景里有几个雷:
- PT 下载器绝对不能乱升。qBittorrent 4.6.7 和 Transmission 4.0.5 是故意钉死的版本,PT 站对客户端版本有白名单,自动升级一时爽,账号被 ban 火葬场;
- Vaultwarden、Emby 这类有状态服务,大版本升级偶尔涉及数据迁移,半夜自动升挂了都不知道;
:latest标签偶尔也有翻车版本,晚几天更新反而是优势。
所以最终形态是:Diun 每天检查一遍所有镜像,有新版就推送到 Synology Chat;我看到通知后,想更就 SSH 上去两条命令,不想更就无视。主动权在人手里。
第一步:给 dockerd 配上代理
先解决"拉不动镜像"的底层问题。之前 Container Manager 靠四个国内镜像加速站续命,这些站时好时坏。正好 NAS 上跑着 mihomo(7890 端口映射到主机),让 dockerd 自己走代理就行。
DSM 7.2 的 Container Manager 是 Docker 24.0.2,支持在 daemon 配置里直接写 proxies,不用碰 systemd。配置文件在 /var/packages/ContainerManager/etc/dockerd.json:
{
"data-root": "/var/packages/ContainerManager/var/docker",
"log-driver": "journald",
"log-opts": { "tag": "synology-container" },
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me",
"https://docker.1panel.live",
"https://hub.rat.dev"
],
"storage-driver": "btrfs",
"proxies": {
"http-proxy": "http://127.0.0.1:7890",
"https-proxy": "http://127.0.0.1:7890",
"no-proxy": "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,100.64.0.0/10"
}
}
三个细节:
- 代理地址用
127.0.0.1而不是局域网 IP。mihomo 的 7890 映射在主机所有网卡上,dockerd 是主机进程,走回环就行——上次换路由器网段废掉一串配置的教训还热乎着,能不绑 IP 就不绑; - 镜像加速站保留。拉镜像时先试加速站,全挂了再直连 docker.io 走代理,双保险;
no-proxy把内网段都排掉,免得访问本地 registry 之类的流量绕远路。
改完重启守护进程:
# 注意:会重启所有容器,先确认它们都有 restart 策略
docker ps -a --format '{{.Names}}' | xargs -I{} docker inspect -f '{} {{.HostConfig.RestartPolicy.Name}}' {}
systemctl restart pkg-ContainerManager-dockerd.service
十几秒后容器全部自动拉起,docker info | grep -i proxy 能看到代理已生效。
第二步:更新过期容器
我的容器基本都是 compose 管的(每个应用一个目录,/volume5/docker/compose/<app>/),更新就是标准两连:
cd /volume5/docker/compose/plex && docker compose pull && docker compose up -d
Plex、Emby、Navidrome 依次更完,配置和数据都在卷/绑定目录里,无痛。
唯一的例外是 cloudflared——当年图省事直接 docker run 起的,没有 compose 文件。重建它有个偷懒神器:一次性 Watchtower。它会拉新镜像,然后用原容器一模一样的配置重建,用完即走:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower --run-once --cleanup cloudflared
日志里 Found new image → Stopping → Creating → Updated=1,十秒搞定,连旧镜像都帮你清了。
qBittorrent 和 Transmission 原地不动,理由上面说过了。
第三步:大扫除
docker image prune -f # 悬空镜像(更新后旧镜像都变成 <none>)
docker builder prune -f # 构建缓存
docker volume prune --all -f # 无容器引用的卷(清之前先 docker volume ls 过一眼!)
孤儿卷清之前我挨个看了内容:6.1GB 是早就删掉的 ChineseSubFinder 留下的缓存,剩下是几个 frpc 和 immich 的残留,都是死数据。三条命令下去:
Total reclaimed space: 1.586GB # 镜像
Total reclaimed space: 900.4MB # 构建缓存
Total reclaimed space: 6.639GB # 卷
9.1GB 回来了。 注意 volume prune 是有杀伤力的,别学我无脑 -f,先确认里面没有你要的东西。
第四步:Diun 更新通知,推到 Synology Chat
Diun 是专门干"镜像有新版就通知"这一件事的工具,天生只报信不动手,比 Watchtower 的 monitor-only 模式更纯粹。它自带 cron,不用碰群晖的任务计划。
/volume5/docker/compose/diun/docker-compose.yml:
services:
diun:
image: crazymax/diun:latest
container_name: diun
restart: always
volumes:
- ./data:/data
- ./notify.sh:/notify.sh:ro
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=Asia/Shanghai
- DIUN_WATCH_WORKERS=5
- DIUN_WATCH_SCHEDULE=0 9 * * * # 每天早上 9 点查一遍
- DIUN_WATCH_JITTER=30s
- DIUN_PROVIDERS_DOCKER=true
- DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true # 默认监控所有运行中容器
- DIUN_NOTIF_SCRIPT_CMD=/notify.sh
# Diun 查 docker hub 的 API 也是国外流量,同样走 mihomo
- HTTP_PROXY=http://192.168.1.48:7890
- HTTPS_PROXY=http://192.168.1.48:7890
- NO_PROXY=localhost,127.0.0.1,192.168.0.0/16
通知渠道我没用 Diun 内置的那堆(Telegram/Discord/Slack……国内环境都别扭),而是用它的 script notifier 调 Synology Chat 的机器人接口——之前折腾 MoviePilot 点播时已经建好了 Chat 机器人,直接复用它的 token,消息发到手机上的 Chat App,零新增依赖。
notify.sh(token 自己换):
#!/bin/sh
# Diun script notifier -> Synology Chat 机器人
WEBHOOK='http://192.168.1.48:5000/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=%22YOUR_BOT_TOKEN%22'
# busybox 环境没有 curl 的 --data-urlencode,手动把 payload percent-encode
enc() {
printf '%s' "$1" | sed \
-e 's/%/%25/g' \
-e 's/{/%7B/g' -e 's/}/%7D/g' \
-e 's/"/%22/g' \
-e 's/\[/%5B/g' -e 's/\]/%5D/g' \
-e 's/:/%3A/g' -e 's/,/%2C/g' \
-e 's;/;%2F;g' -e 's/ /%20/g' -e 's/&/%26/g'
}
TEXT="Docker镜像有更新: ${DIUN_ENTRY_IMAGE} (${DIUN_ENTRY_STATUS})。更新命令: cd compose目录 && docker compose pull && docker compose up -d"
JSON="{\"text\":\"${TEXT}\",\"user_ids\":[4]}" # user_ids 换成你的 Chat 用户 id
wget -q -O- --post-data "payload=$(enc "$JSON")" "$WEBHOOK"
几个踩坑点:
- Chat 机器人的
chatbot接口要的是payload=<JSON>这种 form 格式,JSON 里的特殊字符得 percent-encode,Diun 镜像里只有 busybox 的 wget,没有 curl 的--data-urlencode,所以自己写了个enc(); - 用户 id 怎么查:调机器人的
user_list接口,method=user_list&version=2&token=...,返回里有每个人的user_id; - Diun 主进程走代理查 registry,但 busybox wget 不认大写的
HTTP_PROXY变量,发通知恰好直连 NAS 本机,歪打正着不用配 no_proxy; - 部署完用
docker exec diun diun notif test发条测试消息,确认链路再收工。
起来之后日志很舒服:
Found 10 image(s) to analyze provider=docker
...
Jobs completed added=10 failed=0 skipped=0 unchanged=0
Cron initialized with schedule 0 9 * * *
Next run in 11 hours (2026-07-03 09:00)
首次运行会把当前所有镜像的 digest 记进本地数据库当基线,之后哪个镜像在 registry 上发了新版,第二天早上九点手机就会"叮"一声。
最终形态
| 环节 | 方案 |
|---|---|
| 拉镜像 | 国内加速站优先,失败回落 docker.io 走 mihomo 代理 |
| 发现更新 | Diun 每天 09:00 扫描,推送 Synology Chat |
| 执行更新 | 人工决定:docker compose pull && up -d |
| 禁区 | PT 下载器版本钉死,永不自动升级 |
| 清理 | image prune + builder prune 随手跑,volume prune 看清楚再跑 |
整套东西没有引入任何面板类服务,增量只有一个 20MB 的 Diun 容器。镜像更不更新从"想起来才查"变成"有更新自动找上门",这事儿算是闭环了。