NAS 上 Immich 升级翻车:相册索引、数据库和 Compose 回滚记录

这次记录一个自托管相册的升级现场。

环境是一台 NAS 加一台小主机,Immich 用 Docker Compose 跑。升级前服务正常,照片上传、缩略图、人脸识别都没问题。升级后 docker compose up -d 没报错,页面也能打开,但新照片没有缩略图,重复照片任务也没动静。

一开始我也以为是 Immich 新版本的问题。后来按层拆,发现真正有用的顺序是:镜像版本先固定,数据库备份先确认,UPLOAD_LOCATION 再检查,最后才看 machine learning 和任务队列。

先把 Compose 现场留住

自托管服务升级前,我现在会先做三件事:

bash 复制代码
cp docker-compose.yml docker-compose.yml.$(date +%Y%m%d-%H%M)
cp .env .env.$(date +%Y%m%d-%H%M)
docker compose ps

如果 .env 里用的是:

env 复制代码
IMMICH_VERSION=v2

它的好处是跟随 v2 主版本,坏处是复盘时不够精确。维护窗口里我更倾向于先记下当前实际镜像:

bash 复制代码
docker image ls | grep immich
docker compose config | sed -n '/image:/p'

镜像拉取只是一层,但这一层要先过

Immich 的 Compose 里不是一个镜像。常见会涉及:

  • ghcr.io/immich-app/immich-server
  • ghcr.io/immich-app/immich-machine-learning
  • ghcr.io/immich-app/postgres
  • docker.io/valkey/valkey

NAS 网络环境如果访问 GHCR 不稳,就会在升级时制造很隐蔽的问题:server 镜像拉到了,machine learning 没拉到;database 镜像还是旧的;或者不同机器上镜像 tag 看起来一样,实际 digest 不一致。

所以我会先把镜像层单独验掉:

bash 复制代码
docker pull ghcr.1ms.run/immich-app/immich-server:v2
docker pull ghcr.1ms.run/immich-app/immich-machine-learning:v2
docker pull ghcr.1ms.run/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0

这里用毫秒镜像只是为了把 GHCR 镜像入口跑稳。它不解决挂载权限,也不解决数据库迁移。镜像层过了,后面继续排。

我的 Compose 里会这样写:

yaml 复制代码
services:
  immich-server:
    image: ghcr.1ms.run/immich-app/immich-server:${IMMICH_VERSION:-v2}
    volumes:
      - ${UPLOAD_LOCATION}:/data
      - /etc/localtime:/etc/localtime:ro

  immich-machine-learning:
    image: ghcr.1ms.run/immich-app/immich-machine-learning:${IMMICH_VERSION:-v2}
    volumes:
      - model-cache:/cache

  database:
    image: ghcr.1ms.run/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data

数据库和照片目录要分开备份

自托管相册最容易犯的错,是以为"备份了数据库就等于备份了照片"。Immich 官方文档说得很明确:数据库里有路径和用户元数据,但数据库备份不包含照片和视频原文件。

我的升级前检查表现在是这样:

bash 复制代码
grep -n "UPLOAD_LOCATION\\|DB_DATA_LOCATION\\|IMMICH_VERSION" .env
du -sh "$UPLOAD_LOCATION" "$DB_DATA_LOCATION"
ls -lah "$UPLOAD_LOCATION/backups" | tail

同时确认这些目录还在:

text 复制代码
backups
encoded-video
library
profile
thumbs
upload

如果 NAS 外接盘、NFS、SMB、iSCSI 这类存储在升级前后路径变化,Immich 页面能打开也没用,后台任务会对着空目录或无权限目录工作。

索引卡住不一定是卡死

这次最容易误判的地方是数据库索引。

Immich 官方升级文档提到,VectorChord 相关迁移中,Reindexing clip_indexReindexing face_index 在照片量大或硬件比较弱时可能持续一段时间。也就是说,看到这两行不代表一定挂了。

我会这样看:

bash 复制代码
docker compose logs -f immich-server
docker compose logs -f database

如果日志只是停在 reindex,没有报错,就先看 CPU、磁盘 IO 和数据库容器状态:

bash 复制代码
docker stats
docker compose ps database

如果出现连接失败、迁移失败、权限错误,再进入下一层。

存储权限比想象中常见

升级后缩略图不生成,我最先看 /data

bash 复制代码
docker compose exec immich-server sh -lc 'ls -lah /data | head'
docker compose exec immich-server sh -lc 'touch /data/.write-test && rm /data/.write-test'

如果这里写不进去,后面就不用排 ML 了。相册服务能打开,只说明 server 启动了,不说明它能写缩略图、转码文件和缓存。

常见问题有几个:

现象 常见原因
/data 为空 UPLOAD_LOCATION 相对路径变了
thumbs 不更新 目录权限不对
外部图库消失 挂载路径变化
新照片不处理 job queue 卡住或无法写派生文件
人脸识别不动 machine learning 容器异常

回滚不要只回滚容器

如果确实需要回滚,不要只把镜像 tag 改回去。Immich 官方文档提醒过,切回更早版本并不受支持,尤其涉及数据库迁移时风险更高。

我的处理原则:

  1. 没有数据库备份,不做破坏性回滚。
  2. 先保留当前日志和 Compose 文件。
  3. 确认备份版本和目标版本匹配。
  4. 如果要恢复,数据库和 UPLOAD_LOCATION 要一起考虑。
  5. 先在临时目录或测试实例恢复一遍,再动主服务。

复盘

Immich 这种自托管相册,升级难点不在"容器能不能启动",而在"照片业务能不能继续运转"。页面能打开只是第一步,缩略图、搜索、人脸、重复照片、备份和权限才是后面的长链路。

毫秒镜像在这次排查里只出现一次:GHCR 镜像入口预检。它把升级窗口里最基础的镜像拉取问题提前排掉,让后面的时间集中在数据库、存储和任务队列上。这个边界要清楚,文章也更像工程复盘,而不是把所有问题都归到镜像源。

相关推荐
Rust研习社14 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒14 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro15 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax15 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH15 小时前
Koa和Express的区别
后端
MariaH15 小时前
Koa框架的使用
后端
luckdewei16 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某18 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy18 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom18 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github