手机存储又满了。家里人的照片散落在几部手机、旧硬盘和网盘里,网盘会员每年续费也心疼。Immich 我观望了很久,一直顾虑它更新太快、时不时有破坏性变更。去年秋天它发布了 2.0 正式版,宣布进入稳定阶段,我觉得是时候上车了。

它最吸引我的地方:手机 App 能自动备份,时间线浏览很流畅,有人脸识别和按内容搜索(比如搜“猫”“海边”),用起来很接近商业相册。

存储规划

部署前先想清楚数据放哪,这比装软件本身更重要:

  • 程序跑在 Docker 虚拟机(192.168.10.21)上,目录按compose 目录约定放在 /srv/compose/immich。
  • 照片原图(.env 里的 UPLOAD_LOCATION)放在 NAS 的 ZFS 数据集 tank/photos 上,通过 NFS 挂进虚拟机。ZFS 快照按之前定的快照策略自动打,误删了也能找回来。
  • 数据库(DB_DATA_LOCATION)放在虚拟机的本地磁盘上。官方文档明确说数据库目录不要放在网络共享上,否则容易出各种诡异的问题。

部署

Immich 的 compose 文件变化比较频繁,不建议自己手写或者照抄别人的。按官方文档,从发布页下载当前版本的 docker-compose.yml 和 example.env 两个文件(文档里给了现成的下载命令),把 example.env 重命名为 .env,再改这几项:

# /srv/compose/immich/.env 里我改动的部分
UPLOAD_LOCATION=/mnt/photos/immich
DB_DATA_LOCATION=/srv/compose/immich/postgres
# 默认是 release(总是最新版),我改成了当时的具体版本号,升级前先看更新说明
IMMICH_VERSION=v2.x.y
# 只用字母和数字,不要带特殊字符
DB_PASSWORD=换成随机生成的字母数字串
TZ=Asia/Shanghai

NFS 的部分,NAS 那边把 /tank/photos 导出给 Docker 虚拟机,虚拟机这边写进 fstab:

# NAS 上的 /etc/exports
/tank/photos  192.168.10.21(rw,sync,no_subtree_check,no_root_squash)

# Docker 虚拟机上的 /etc/fstab
192.168.10.20:/tank/photos  /mnt/photos  nfs  defaults,_netdev  0  0

Immich 的容器默认以 root 运行,如果 NFS 开着默认的 root_squash,root 写进去的文件会被映射成 nobody,经常出现写不进去的情况,所以我只对这一个 IP 开了 no_root_squash。

然后 docker compose up -d,等几个容器都起来后打开 http://192.168.10.21:2283,第一个注册的账号就是管理员。

从手机迁移

迁移分两部分。

手机上现有的照片,直接用 Immich 的手机 App:服务器地址填 http://192.168.10.21:2283,登录后在备份设置里选要备份的相册。我只选了相机胶卷和几个重要的相册,聊天软件自动保存的图片就算了。几万张照片第一次上传要很久,我是睡前插上充电器、连着家里的 Wi-Fi、让 App 停在前台跑,两个晚上传完。之后打开后台备份,新拍的照片就会自动传上去。

以前导出到电脑和旧硬盘上的照片,用官方的命令行工具批量上传更快:

# 需要较新版本的 Node.js
npm i -g @immich/cli

# API Key 在网页的“账户设置 → API 密钥”里生成
immich login http://192.168.10.21:2283/api 你的API密钥

# 先用 --dry-run 看看会传哪些文件;--album 会按文件夹名自动建相册
immich upload --recursive --album --dry-run /data/old-photos
immich upload --recursive --album /data/old-photos

CLI 会先计算文件的校验和,服务器上已经有的会跳过,所以中断了重跑也没关系。

踩过的坑

1. NFS 没挂上,Docker 就先启动了。 有一次虚拟机重启时 NAS 还没开机,NFS 没挂上,Immich 照样启动,往本地那个空的 /mnt/photos 目录里写了一堆东西;等 NFS 挂上之后,这些文件就被“盖住”了。解决办法是让 Docker 依赖这个挂载点,用 systemctl edit docker.service 加一段覆盖配置:

[Unit]
RequiresMountsFor=/mnt/photos

代价是 NFS 挂不上时所有容器都不会启动。不过对我来说,这总比数据写错地方要好。

2. 没有拍摄时间的图片跑到了“今天”。 截图、从聊天软件里保存的图片很多没有 EXIF 拍摄时间,Immich 只能退而使用文件的修改时间,而我在拷贝过程中把修改时间弄丢了,结果几千张图都堆在了导入那天。补救办法是导入前用 exiftool 从文件名里提取时间写回 EXIF;已经导入的,也可以在网页上批量修改日期。

3. 刚导入时机器忙不过来。 导入后 Immich 要给每张照片生成缩略图、做人脸检测和内容识别。Docker 虚拟机只有两个核,CPU 满载了两天,笔记本风扇一直在叫。可以在管理后台的任务页面暂停某些任务或者调低并发,等夜里再跑。

4. 重复照片。 手机 App 备份和命令行导入难免有重叠。完全相同的文件 Immich 会自动跳过,但经过压缩的副本就认不出来了。新版本里有一个查找重复照片的工具,会把相似的照片分组列出来,人工逐组确认删除即可。

备份

照片是家里最不能丢的数据。Immich 自己会定时把数据库导出到上传目录下的 backups 子目录里,所以重点备份好 UPLOAD_LOCATION 就行。上传目录里的缩略图(thumbs)和转码视频(encoded-video)都可以重新生成,备份时我把它们排除掉,原图再用 restic 推一份到对象存储。

小结

用了一个月,家里人已经习惯在 Immich 里翻照片了,按人物和地点浏览的功能尤其受欢迎。目前只在家里局域网用,手机在外面拍的照片,回家连上 Wi-Fi 后自动同步,对我来说正合适。