DryDock 分布式容器更新监控部署实录:一屏管五台机器

事情是这样的。我有 5 台机器跑着 Docker,分布在 NAS、内网几台小主机、还有一台荷兰 VPS。每次更新镜像要 SSH 进去逐个 docker compose pull,漏了哪台全靠运气发现。

调研了一圈镜像更新监控工具,Watchtower 太粗暴(动不动自动重启),Diun 只通知不管理。最后选了 DryDock—— 一个 Controller 管所有 Agent,Web UI 统一查看,支持 23 个镜像注册表,20+ 通知渠道。

环境:fnOS NAS(Controller)、AI 主机(.216)、n8n 工具机(.181)、Bandwagon VPS(云主机)。Frigate NVR(.160)暂缓。

架构

┌─────────────────────────────────────┐
│ fnOS (<NAS_IP>) │
│ DryDock Controller :24300 │
│ 统一面板 · 通知 · 自动更新 │
│ agent.env ← 所有 Agent 连接配置 │
└──┬──────────┬──────────┬────────────┘
│ │ │
▼ ▼ ▼
Agent Agent Agent
:3000 :3000 :34300
AI主机 n8n Bandwagon
.216 .181 云主机
✅ ✅ ✅

通信方式:Controller 主动连 Agent(SSE over HTTP)。Agent 只需要 Docker socket 访问,不暴露给外部 —— 通过 docker-socket-proxy 做一层隔离。

Controller 配置(fnOS)

Controller 跑在 NAS 上,核心是两个容器:DryDock 本体 + docker-socket-proxy(用于监控本机容器)。

services:
drydock:
image: codeswhat/drydock
container_name: drydock
restart: unless-stopped
env_file:
- agent.env # ← Agent 连接独立文件
depends_on:
socket-proxy:
condition: service_healthy
environment:
- DD_WATCHER_LOCAL_HOST=socket-proxy
- DD_WATCHER_LOCAL_PORT=2375
- DD_AUTH_BASIC_ADMIN_USER=***
- "DD_AUTH_BASIC_ADMIN_HASH=***"
35 collapsed lines
ports:
- 24300:3000
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
security_opt:
- no-new-privileges:true
socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: socket-proxy
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- CONTAINERS=1
- IMAGES=1
- EVENTS=1
- SERVICES=1
- POST=1
- NETWORKS=1
- INFO=1
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
interval: 5s
timeout: 3s
retries: 3
start_period: 5s

agent.env 设计

Agent 连接配置不写在 docker-compose.ymlenvironment 里,而是拆到独立的 agent.env

# === AI 主机 (<AI_HOST_IP>) ===
DD_AGENT_AI_HOST=<AI_HOST_IP>
DD_AGENT_AI_SECRET=***
# === n8n 工具机 (<N8N_HOST_IP>) ===
DD_AGENT_N8N_HOST=<N8N_HOST_IP>
DD_AGENT_N8N_SECRET=***
# === Bandwagonhost VPS (<VPS_IP>) ===
DD_AGENT_VPSNL2_HOST=<VPS_IP>
DD_AGENT_VPSNL2_PORT=34300
DD_AGENT_VPSNL2_SECRET=***
DD_AGENT_ALLOW_INSECURE_SECRET=*** # LAN 明文 HTTP

Agent 部署模板

每台目标机器上跑两个容器:docker-socket-proxy + drydock-agentcommand: --agent)。

services:
socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: drydock-socket-proxy
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- CONTAINERS=1
- IMAGES=1
- EVENTS=1
- SERVICES=1
- POST=1
healthcheck:
test: wget --spider http://localhost:2375/version || exit 1
18 collapsed lines
interval: 5s
timeout: 3s
retries: 3
drydock-agent:
image: codeswhat/drydock
command: --agent
container_name: drydock-agent
restart: unless-stopped
depends_on:
socket-proxy:
condition: service_healthy
ports:
- "3000:3000"
environment:
- DD_AGENT_SECRET=*** # 和 Controller agent.env 一致
- DD_WATCHER_LOCAL_HOST=socket-proxy
- DD_WATCHER_LOCAL_PORT=2375

已部署的 Agent 位置:

机器路径
AI 主机 (.216)/opt/drydock-agent/
n8n (.181)/opt/drydock-agent/
Bandwagon VPS(云主机)用户自建,端口 34300

新机器加入流程

  1. SSH 到目标机器
  2. 创建 /opt/drydock-agent/docker-compose.yml(复制上方模板)
  3. 确认 DD_AGENT_SECRET 与 Controller 的 agent.env 一致
  4. docker compose up -d
  5. 验证:curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 → 应返回 401(正常,没有认证头)
  6. 在 Controller 的 agent.env 追加对应行(端口非 3000 时加 _PORT=xxxx
  7. Controller 重建:docker compose up -d
  8. 打开 http://<NAS_IP>:24300,左侧栏应出现新 Agent

通知配置

DryDock 支持 20+ 通知渠道(ntfy、Telegram、Lark、Discord 等)。当前配的是自建邮箱 <mail.your-domain.com>,汇总模式(batch),所有容器更新合成一封邮件。

DD_NOTIFICATION_SMTP_MAIN_HOST=<mail.your-domain.com>
DD_NOTIFICATION_SMTP_MAIN_PORT=465
DD_NOTIFICATION_SMTP_MAIN_FROM=<your@email.com>
DD_NOTIFICATION_SMTP_MAIN_TO=<your@email.com>
DD_NOTIFICATION_SMTP_MAIN_USER=<your@email.com>
DD_NOTIFICATION_SMTP_MAIN_PASS=***
DD_NOTIFICATION_SMTP_MAIN_MODE=batch

三种通知模式:

模式效果
simple每个容器一条(刷屏)
batch全部汇总一条 ✅
digest每天定时摘要

踩坑记录

env_file 中的 $ 被 shell 吃掉

YAML compose 文件通过 SSH + heredoc 传到远程时,$argon... 变成了 argon...(丢了一个 $)。SSH + shell 对 $ 做了二次解释。解决:用 Python yaml.dump 写到远程,完全绕过 shell。

Agent 端口不匹配

VPS 上 Agent 映射了 34300:3000,但 Controller 的 agent.env 没指定端口,默认连 3000 → 连接失败。解决:加 DD_AGENT_{name}_PORT=34300

Agent 返回 401 是正常的

Agent 收到无认证的请求返回 401。Controller 通过 DD_AGENT_{name}_SECRET 认证后正常通信。用 curl 测试看到 401 说明 Agent 在跑,不是挂了。

与 1Panel 的关系

DryDock 和 1Panel 互补,不是替代:

功能DryDock1Panel
镜像更新检测✅ 23 个注册表
更新通知推送✅ 20+ 通道
容器操作(启停)
网站 / SSL 管理
数据库管理
Docker Compose GUI

有 1Panel 的机器两者都跑;没 1Panel 的机器 DryDock Agent 就够了。

文件清单

fnOS:/vol1/1000/docker/drydock/
├── docker-compose.yml # Controller + socket-proxy
├── agent.env # Agent 连接列表
└── docker-compose.yml.bak
各 Agent 机器:/opt/drydock-agent/
└── docker-compose.yml # 标准模板

总结

DryDock 填补了 Watchtower(太自动)和 Diun(只通知)之间的空白。Controller-Agent 架构对于多机器场景很顺手:加一台机器就是复制一份 compose + 加三行 env,5 分钟的事。

目前 4 台已接入,Frigate NVR 暂缓(Dpanel 托管路径要适配)。后续打算把通知切到 ntfy,邮件通知容易被 Gmail 归到垃圾箱。