先说结论:Watchtower 这个自动更新工具,好用,但不能全放开。
我一开始图省事,让它监控所有容器,每天凌晨检查一遍,有新版就自动拉下来重启。
跑了一个月没事。第二个月,Immich 发了个大版本,Watchtower 夜里自动拉了新镜像,重启。
第二天早上打开相册,一片空白——数据库版本不兼容,正在跑迁移,卡在中间。
我花了三个小时从备份恢复。
这篇写两件事:怎么让 Watchtower 只更新该更新的,还有怎么把 Docker 堆积的几十 G 镜像垃圾清掉。
一、自动更新的风险在哪
Docker 的镜像是滚动更新的,同一个 tag 在不同时间拉到的是不同版本。
latest 这种 tag 尤其危险:今天拉是 2.x,明天拉可能是 3.x,中间可能包含破坏性的数据库迁移。
被自动更新搞崩过的,我知道的有:
- Immich:大版本升级要跑数据库迁移,中途 OOM 就卡死
- Nextcloud:升级必须按顺序一个个版本走,跳版本直接崩
- Jellyfin:一般没事,但偶尔配置目录结构变了
- Portainer:一般没事
所以原则是:数据库类的、有版本迁移的,绝不自动更新。
二、Watchtower 的配置(白名单模式)
services:
watchtower:
container_name: watchtower
image: containrrr/watchtower:latest
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=Asia/Shanghai
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=86400
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_ROLLBACK=true
mem_limit: 128m
参数解释:
| 参数 | 作用 |
|---|---|
WATCHTOWER_CLEANUP | 更新完删旧镜像 |
WATCHTOWER_POLL_INTERVAL | 检查间隔,86400 秒 = 24 小时 |
WATCHTOWER_LABEL_ENABLE | 关键:只更新带 label 的容器 |
WATCHTOWER_ROLLBACK | 新版启动失败自动回滚 |
WATCHTOWER_LABEL_ENABLE=true 是核心:开了之后,只有 compose 里标了 watchtower.enable=true 的容器才被更新,其余一律不管。
然后给想自动更新的服务加标签:
services:
uptime-kuma:
image: louislam/uptime-kuma:1
labels:
- "com.centurylinklabs.watchtower.enable=true"
我的白名单:
| 容器 | 自动更新 | 原因 |
|---|---|---|
| Uptime Kuma | 是 | 无数据库迁移风险 |
| Nginx Proxy Manager | 是 | 配置在 /data,更新安全 |
| Portainer | 是 | 自身无状态 |
| Alist | 是 | 配置独立 |
| qBittorrent | 是 | 无迁移 |
| Immich | 否 | 有数据库迁移 |
| Vaultwarden | 否 | 密码库,绝不冒险 |
| 数据库 | 否 | 永远手动 |
三、为什么数据库绝对不能自动更新
Postgres 大版本之间不能直接升级,要 dump 再 restore,或者用专门的迁移工具。
Watchtower 只会拉新镜像重启容器,不会做迁移。
结果就是:新版本 Postgres 面对旧版本的数据目录,要么起不来,要么把目录搞坏。
Immich 那次就是这个问题——它自带的 Postgres 镜像换了基础版本。
四、手动更新的正确姿势
对于有状态的容器,更新流程固定:
# 1. 备份数据
sudo tar czf /backup/immich-$(date +%Y%m%d).tar.gz \
-C /opt/docker/immich postgres
# 2. 记录当前镜像版本
docker compose images
# 3. 拉新镜像
docker compose pull
# 4. 重启
docker compose up -d
# 5. 看日志,确认没报错
docker compose logs -f --tail=100
如果起不来,回滚:
docker compose down
# 恢复备份的 postgres 目录
sudo tar xzf /backup/immich-20261020.tar.gz -C /opt/docker/immich
docker compose up -d
五、磁盘清理:这才是刚需
8G 内存的 Mac mini,系统盘 500G,跑了大半年,df -h 一看,用了 78%。
查了一下:
docker system df
输出大概是这样:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 31 9 18.7GB 13.2GB (70%)
Containers 12 9 2.1GB 300MB (14%)
Local Volumes 18 10 6.4GB 2.2GB (34%)
Build Cache 0 0 0B 0B
镜像 18.7G,其中 13.2G 是可回收的——全是历史版本残留。
清理命令:
# 清理停止的容器、悬空镜像、无用网络
docker system prune -f
# 连同未使用的卷一起清(危险,先确认没有数据在里面)
docker system prune -af --volumes
# 只清镜像
docker image prune -a -f
我第一次跑 docker system prune -f,清出来 9.8G。
注意 -a 会把所有没有被容器使用的镜像删掉,包括你手动 pull 但还没启动的。建议先 docker system df 看清楚。
六、定时清理
手动清不行,得自动化。
写个脚本 /opt/docker/clean.sh:
#!/bin/bash
# 每周日 凌晨 3 点清理
LOG=/var/log/docker-clean.log
echo "[$(date '+%F %T')] 开始清理" >> $LOG
# 记录清理前
docker system df >> $LOG
# 只清悬空镜像和停止的容器,不动体积
docker system prune -f >> $LOG 2>&1
# 清理 30 天前的悬空镜像
docker image prune -f --filter "until=720h" >> $LOG 2>&1
echo "[$(date '+%F %T')] 清理完成" >> $LOG
docker system df >> $LOG
加执行权限,配 cron:
sudo chmod +x /opt/docker/clean.sh
sudo crontab -e
加一行:
0 3 * * 0 /opt/docker/clean.sh
每周日凌晨三点跑。
七、镜像怎么省空间
除了清理,还有几个办法:
1. 用 alpine 版本
很多官方镜像有 alpine 变体,体积小一个数量级。比如 redis:6.2-alpine 是 30M,redis:6.2 是 130M。
2. 别用 latest
用具体版本号,比如 jc21/nginx-proxy-manager:2.11.3。这样拉的时候不会重复下载,也避免意外升级。
3. 定期看 docker system df
我设了个提醒,每月看一次。低于 70% 使用率就放心。
八、我这半年的实际数据
| 项目 | 数值 |
|---|---|
| 容器数量 | 9 个常驻 |
| 镜像总大小 | 18.7G |
| 清理后镜像 | 8.9G |
| 释放空间 | 9.8G |
| 系统盘使用率 | 从 78% 降到 71% |
| 清理频率 | 每周一次 |
照片和视频库另算,那个在另一块盘上,占的是真实存储,不是垃圾。
最后
自动更新和自动清理,本质上是同一件事:让维护变懒,但别懒到出事。
有状态的、带数据库的,手动更新 + 先备份,这十几分钟省不了。
无状态的、配置在卷里的,扔给 Watchtower 管,省心。
下一篇是这一季的收尾——9 个容器跑在 8G 内存的老 Mac mini 上,我实测算了一遍它到底撑不撑得住。
---
如果你也在折腾这套服务,碰到卡住的地方,可以加我微信 lige0552 聊聊。白天要上班,消息回得慢,看到都会回。