先说结论:Watchtower 这个自动更新工具,好用,但不能全放开。

我一开始图省事,让它监控所有容器,每天凌晨检查一遍,有新版就自动拉下来重启。

跑了一个月没事。第二个月,Immich 发了个大版本,Watchtower 夜里自动拉了新镜像,重启。

第二天早上打开相册,一片空白——数据库版本不兼容,正在跑迁移,卡在中间。

我花了三个小时从备份恢复。

这篇写两件事:怎么让 Watchtower 只更新该更新的,还有怎么把 Docker 堆积的几十 G 镜像垃圾清掉。

一、自动更新的风险在哪

Docker 的镜像是滚动更新的,同一个 tag 在不同时间拉到的是不同版本。

latest 这种 tag 尤其危险:今天拉是 2.x,明天拉可能是 3.x,中间可能包含破坏性的数据库迁移。

被自动更新搞崩过的,我知道的有:

所以原则是:数据库类的、有版本迁移的,绝不自动更新。

二、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 聊聊。白天要上班,消息回得慢,看到都会回。