部署配方 · 媒体
Pi 4 上的 Immich:一个能熬过换手机的相册
Immich 补上了人们真正舍不得 Google Photos 的那部分:手机自动上传、能一直往下滚的时间线、可以分享的相册、还有人脸识别。它同时也是这台机架上最重的服务:四个容器,加一个需要官方镜像里拿不到的扩展的 Postgres。下面是能跑在 8GB Pi 4B 上的版本,包含两个能避免它把整个下午都耗在重跑机器学习流水线上的设置。
| 容器镜像 | ghcr.io/immich-app/immich-server:v1.129.0 |
|---|---|
| 宿主端口 | :2283/tcp |
| 数据目录 | /srv/homelab/immich/library |
| 内存预留 | 1300 MB |
| CPU 上限 | 1.5 vCPU (cpus: "1.5") — the machine learning container is the only reason it needs more than one |
| 更新节奏 | monthly. Immich ships breaking changes with a migration script; read the release notes before pulling, and never skip a release that carries a migration |
| ARM 兼容 | arm64 native for both server and machine learning containers; CLIP and face models run on CPU and a Pi 4 spends about four seconds per new photo on them |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :2283 | 2283 | tcp | 仅局域网 | web UI, the phone apps and the upload API |
部署步骤
按各容器的要求建好四个目录
服务端以自己的 uid 写入,Postgres 需要 999,机器学习缓存目录只要存在即可。Postgres 的属主搞错,容器会一直重启,日志里是一行权限错误。
run sudo mkdir -p /srv/homelab/immich/{library,db,model-cache} sudo chown -R 1000:1000 /srv/homelab/immich/library sudo chown -R 999:999 /srv/homelab/immich/db把数据库密码生成到 env 文件里
这套里每个容器都从 compose 的环境变量读同一个密码。生成一次、之后再也不手打,是让它们保持一致的唯一办法。
run echo "IMMICH_DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=')" >> /srv/homelab/.env chmod 600 /srv/homelab/.env先起数据库,等健康检查通过
服务端启动时会执行数据库迁移。对着一个还在初始化的数据库启动它,会得到看起来像数据库损坏的迁移错误。
run cd /srv/homelab/immich && docker compose up -d immich-db immich-redis until docker exec immich-db pg_isready -U immich; do sleep 2; done docker compose up -d建好家人账号,然后关闭注册
用注册表单创建的第一个账号会成为管理员。把其他人加完,再去管理设置里关掉注册——一次点击,实例就对陌生人关上了门。
run # 管理 -> 设置 -> 用户设置 -> 关闭「注册」 # 然后在 管理 -> 用户 -> 创建用户 里逐个建账号分阶段导入,并且盯着温度
被动散热的 Pi 4 在大批量导入时会冲到 80 度并开始降频,结果导入变得更慢。第一次导入时看着温度,考虑先加个风扇。
run vcgencmd measure_temp docker stats --no-stream immich-server immich-ml
四个容器,各自花掉多少
这套包含一个服务端、一个带向量扩展的 Postgres、一个 Redis 队列,以及一个机器学习工作进程。在树莓派上,需要认真考虑的是机器学习那个:它给每张新照片做 CLIP 向量和人脸检测要花大约四秒 CPU 时间,手机上传二十张无所谓,第一次导入四万张就很痛苦。
| 容器 | 内存 | 职责 | 能不能砍掉 |
|---|---|---|---|
| immich-server | 1.1 GB | API、网页界面、缩略图、转码 | 不能 |
| immich-db | 400 MB | 带 vectorchord 和 pgvecto.rs 的 Postgres | 不能 |
| immich-redis | 45 MB | 缩略图与机器学习的任务队列 | 不能 |
| immich-ml | 峰值 700 MB | CLIP 搜索与人脸识别 | 能,代价是没有搜索和人脸 |
第一次导入才是难关
开着机器学习导入四万张照片,Pi 4 大约要两天后台时间,期间板子发烫、上面其它服务都变慢。分阶段做要好得多:先关掉机器学习导入,等缩略图和元数据跑完,再在夜里开启机器学习,让它分批把库过一遍。
任务并发设置是让这件事变得可忍受的关键。默认值是按有富余核心的服务器定的;在派上把缩略图和机器学习的并发都设成 1,机架其它部分才不会跟着卡。
docker exec -it immich-server immich-admin list-jobs
# 然后在管理界面:设置 -> 任务设置 -> 缩略图生成 / 智能搜索
# 并发设为 1,并且手动启动任务,别让它自己排队为什么数据库不能用官方 Postgres 镜像
Immich 的智能搜索把图像向量存成向量类型,并按余弦距离查询。这需要 vectorchord 和 pgvecto.rs 扩展,而官方 postgres 镜像里没有,所以项目自己发布了一个镜像。换成朴素的 postgres:16,你会得到一个能启动、上传也正常、但一跑智能搜索就报错的服务端。
同样的原因,这个数据库也不是能随手搬到托管服务上的东西。数据卷留在本地固态盘,pg_dump 的备份例程也要留着。
docker exec immich-db psql -U immich -d immich -c '\dx'
# 列表里应该能看到 vectorchord 和 vectors 两个扩展存储布局与外部媒体库的问题
默认情况下 Immich 会把每次上传复制进它自己的媒体库目录,并按用户和日期组织。这是对的默认行为:文件归它管,数据库是唯一事实来源。
外部媒体库则是指向 NAS 上已有目录、就地索引。手里已经有一份 300GB 照片目录的人想要的就是这个,而且它确实能用——但那个目录必须以只读挂载,否则 Immich 会按它自己的结构去移动和重命名文件,而你对那套结构本来是有想法的。
# 外部媒体库挂载,故意设成只读
- /mnt/nas/photos-archive:/mnt/photos-archive:rocompose 文件
整份文件放在 /srv/homelab/immich/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
immich-server:
image: ghcr.io/immich-app/immich-server:v1.129.0
container_name: immich-server
restart: unless-stopped
ports:
- "2283:2283/tcp"
environment:
TZ: Asia/Shanghai
DB_HOSTNAME: immich-db
DB_USERNAME: immich
DB_PASSWORD: ${IMMICH_DB_PASSWORD}
DB_DATABASE_NAME: immich
REDIS_HOSTNAME: immich-redis
IMMICH_MACHINE_LEARNING_URL: http://immich-ml:3003
volumes:
- /srv/homelab/immich/library:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
depends_on:
immich-db:
condition: service_healthy
networks: [rack]
immich-ml:
image: ghcr.io/immich-app/immich-machine-learning:v1.129.0
container_name: immich-ml
restart: unless-stopped
volumes:
- /srv/homelab/immich/model-cache:/cache
networks: [rack]
immich-redis:
image: redis:7.4-alpine
container_name: immich-redis
restart: unless-stopped
networks: [rack]
immich-db:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
container_name: immich-db
restart: unless-stopped
environment:
POSTGRES_USER: immich
POSTGRES_PASSWORD: ${IMMICH_DB_PASSWORD}
POSTGRES_DB: immich
volumes:
- /srv/homelab/immich/db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U immich"]
interval: 10s
timeout: 5s
retries: 6
networks: [rack]
networks:
rack:
external: true加固清单
- 建完家庭成员的账号后关闭注册,任何人找到这个网址也进不来
- 上传只在局域网内或通过 WireGuard 进行,不做端口转发
- 数据库只在容器网络内监听,完全不发布端口
- 机器学习容器不发布端口,除首次启动下载模型外不需要外网
备份方案
媒体库就是一棵结构可读的普通目录,每晚用 rsync 同步到 NAS,再由 NAS 负责异地备份。数据库要单独用 pg_dump 导出——没有它,相册、人脸、以及文件与日期之间的对应关系都会丢失,媒体库就退化成一堆命名正确但毫无组织的文件。
上线后验证
- 手机 App 上传的照片几秒内出现在时间线里,并且生成了缩略图
- 智能搜索一个明显的词比如「狗」能出结果,说明向量扩展和机器学习进程都在工作
- 每晚的 pg_dump 能恢复到临时库里,且素材条数一致
- 磁盘上的媒体库目录里是能读懂的年份文件夹,即使没有数据库这些文件也是可用的
踩过的坑
- 把官方发布的 Postgres 镜像换成通用镜像,智能搜索一跑就会报扩展错误,而界面上没有任何地方会告诉你「数据库镜像不对」。
- 在大库的第一次导入时就开机器学习,会让这台派当两天的暖风机。先导入,后索引。
- 把外部媒体库挂成可写,就是人们某天早上醒来发现归档被重新整理的原因。挂成只读,让 Immich 去索引而不是去管理。
- 视频转码设置默认开启硬件加速,而树莓派在多数编码格式上并没有对应的硬件。保持软件转码并把并发调低,比和预设较劲省事。
硬件与选型问答
- Pi 4 真的够跑 Immich 吗,还是该交给 NAS?
- 8GB 的 4B 能处理五到八万张的家庭库,前提是分阶段导入、机器学习并发保持为 1。x86 的 NAS 会更快也更凉快,但那也意味着你的照片库和备份存在同一台机器上。
- 第一次导入到底要多久?
- 缩略图和元数据:Pi 4 上大约每分钟两百张,四万张约三个半小时。开启机器学习后,整个库每张再加约四秒,这就是「两天」这个数字的来源。
- 能不能一边继续用 Google Photos?
- 可以,而且头一个月就应该这样。两边同时跑,并行上传到 Immich,等你能从 Immich 里真正恢复一张照片、确认备份可用之后,再取消另一个订阅。这次迁移可不是按人们以为的方向能回头的。