Immich 多实例水平扩展实战:共享基础设施、Worker 拆分与弹性缩减
本文围绕 Immich 官方的 Scaling Immich 指南展开,讲解如何将 Immich 后端从单容器扩展到多实例并行部署。读完本文,你将理解 Immich 水平扩展的唯一硬性前提(Postgres、Redis 与文件系统三重共享)、如何通过 IMMICH_WORKERS_INCLUDE / IMMICH_WORKERS_EXCLUDE 环境变量拆分 API 与微服务 Worker,以及为什么停掉任意一个实例都不会丢失任何数据——并附有对应源码级实现证据。
多实例扩展的核心前提:共享基础设施
Immich 采用现代化部署实践构建,其后端在设计上就支持多个实例并行运行。官方指南指出,多实例部署唯一需要牢记的要求是:每个实例都必须连接到同一套共享基础设施,具体包括:
- 所有实例访问同一个 Postgres 数据库;
- 所有实例访问同一个 Redis 实例;
- 所有实例的容器内挂载相同的文件目录(照片/视频原文件、缩略图等)。
这一点可以从生产 compose 文件中得到印证:docker/docker-compose.prod.yml 中 immich-server 服务通过 ${UPLOAD_LOCATION}/photos:/data 挂载共享存储,并声明 depends_on: redis 与 depends_on: database。这意味着当你复制出第二个 immich-server 实例时,必须保证它指向同样的 POSTGRES_HOSTNAME / REDIS_HOSTNAME(或对应的 URL 环境变量),并挂载同一份 /data 目录,两个实例才能像一台机器的两部分一样协同工作。
官方文档同时给出了两类典型的扩展动机:
- 你有一台带强力 GPU 的游戏 PC,想让它专门承担视频转码和缩略图生成这类 CPU/GPU 密集型任务;
- 你运营着一个跨多台高性能服务器的 Kubernetes 集群,希望把算力用起来。
:::提示(原文档强调)::: 如果你只有一台机器可以运行 Immich,那么扩展到多个容器大概率不会带来任何收益。单个 Immich 容器内部就会并发运行多个后台任务,并且你可以在管理面板中直接调增这些任务的数量。 :::
为什么单机不需要多容器:Worker 架构的源码视角
理解“单机无需扩展”的关键,在于 Immich 服务内部的 Worker 模型。从源码结构看,immich-server 进程本身是一个 Worker 管理器:server/src/main.ts 中的 Workers 类在 bootstrap() 里读取环境配置得到的 Worker 列表,并逐个启动:
// server/src/main.ts
async bootstrap() {
const isMaintenanceMode = await this.isMaintenanceMode();
const { workers } = new ConfigRepository().getEnv();
if (isMaintenanceMode) {
this.startWorker(ImmichWorker.Maintenance);
} else {
await this.waitForFreeLock();
for (const worker of workers) {
this.startWorker(worker);
}
}
}
可用的 Worker 类型定义在 server/src/enum.ts:
export enum ImmichWorker {
Api = 'api',
Maintenance = 'maintenance',
Microservices = 'microservices',
}
也就是说,默认每一个 immich-server 容器内部已经同时跑着 api 和 microservices 两个 Worker:api 负责响应 Web 与移动端 App 的数据/文件请求,microservices 负责缩略图生成、视频编码等后台任务(job)。官方 Jobs and Workers 文档对此有专门说明。既然单容器内已是多线程并发处理,单机场景下再拆容器只是增加了编排复杂度,而非吞吐能力。
拆分 Worker:用环境变量决定容器职责
当你确实要扩容(例如加一台转码机),官方推荐的技巧是:新实例可以只保留 microservices Worker、关掉 API Worker。这通过两个环境变量实现,其含义可参考 环境变量文档:
| 变量 | 作用 |
|---|---|
IMMICH_WORKERS_INCLUDE |
只运行这些 Worker(白名单) |
IMMICH_WORKERS_EXCLUDE |
不运行这些 Worker;未指定 INCLUDE 时匹配默认 Worker,指定了则匹配 INCLUDE 的取值 |
这两个变量在 server/src/dtos/env.dto.ts 中声明为可选字符串,其解析逻辑在 server/src/repositories/config.repository.ts:
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
const workers = [...setDifference(includedWorkers, excludedWorkers)];
for (const worker of workers) {
if (!WORKER_TYPES.has(worker)) {
throw new Error(`Invalid worker(s) found: ${workers.join(',')}`);
}
}
从这段实现可以看到三点:
INCLUDE的默认值是api,microservices——即不配置时两个 Worker 全开;- 实际启动集合是
INCLUDE减去EXCLUDE(集合差),且变量按逗号分隔、自动去除空白; - 解析结果会校验合法性,出现未知 Worker 名会直接抛错拒绝启动,防止拼写错误导致容器静默变成“空壳”。
测试用例 server/src/repositories/config.repository.spec.ts 覆盖了 INCLUDE: 'api'、EXCLUDE: 'api'、混合逗号与空格输入、未知 Worker 报错等分支,行为与上述实现一致。
官方给出的 Docker Compose 拆分示例
Jobs and Workers 文档提供了一个“一个容器只跑 Web/API、另一个容器跑全部微服务”的最小拆分方案:先把整个 immich-server 服务块复制为一份新服务并改名(副本上删除端口映射):
- immich-server:
- container_name: immich_server
- ...
- ports:
- - 2283:2283
+ immich-microservices:
+ container_name: immich_microservices
然后为两份服务分别添加环境变量,实现职责隔离:
services:
immich-server:
...
+ environment:
+ IMMICH_WORKERS_INCLUDE: 'api'
immich-microservices:
...
+ environment:
+ IMMICH_WORKERS_EXCLUDE: 'api'
这个模式与 Scaling 指南中“新实例专做转码/缩略图”的场景完全一致:把 immich-microservices 部署到算力更强的那台机器,并确保它与 immich-server 共享 Postgres、Redis 和 /data 挂载即可。
一个容易被忽略但很关键的细节是健康检查的适配。server/bin/immich-healthcheck 在容器启动时会先判断 API Worker 是否被禁用:
if [[ ( $IMMICH_WORKERS_INCLUDE != '' && $IMMICH_WORKERS_INCLUDE != *api* ) || $IMMICH_WORKERS_EXCLUDE == *api* ]]; then
echo "API worker excluded, skipping"
exit 0
fi
即纯 microservices 容器会直接跳过对 http://IMMICH_HOST:2283/api/server/ping 的 ping 检查并视为健康。如果不知道这一点,你可能会误以为拆分后的容器“健康检查失败”而反复排查——实际上这是 Immich 有意为之的行为。
跨机器扩展:官方立场是“原则明确,细节自证”
Scaling 指南坦率地说明:跨机器扩展的具体做法因环境而异,需要一定的运维知识,因此官方不给出统一的分步教程。它给出的原则是:
- 在 Kubernetes 上,扩展可能简单到只需调大 Deployment 的副本数(注意所有副本的镜像、环境变量、PVC/共享卷配置一致);
- 在其他环境,你可能需要配置网络隧道或 NFS 挂载,让多个节点上的容器都能访问同一套 Postgres、Redis 与照片目录。
原文以一句“细节留给读者练习 ;)”收尾——但配合前文的共享基础设施要求,判断标准其实很清晰:任何一台新机器上的容器,只要能读写同一个数据库、同一个 Redis 和同一份文件,它就是一个合格的扩展节点。
向下缩减(Scaling Down):零风险的弹性伸缩
Scaling 指南的另一个亮点是缩减同样安全。由于 Immich 的全部状态都存放在三个地方——Postgres、Redis 与文件系统——停止任意一个正在运行的 immich-server 容器没有任何数据风险。官方给出的真实场景是:你想临时把 GPU 让出来打游戏,直接把那台机器上的 Immich 容器停掉即可。
缩减后的行为边界也很明确:
- 只要还有任意一个
apiWorker 在运行,用户就能正常浏览 Immich(Web/App 的数据请求全部落到它身上); - 正在处理的任务队列会等待,直到有可用的
microservicesWorker 出现才继续被消费——任务本身不会因为节点下线而丢失,因为任务状态在共享的 Postgres/Redis 中。
这实际上构成了一套极简的“弹性伸缩”方案:平时多实例并行消化上传积压,空闲时保留至少一个带 api Worker 的实例维持服务可用性,其余节点随时可以下线回收算力。
小结:扩展决策清单
| 场景 | 建议 |
|---|---|
| 单机部署,想加速后台任务 | 不加容器,直接在管理面板调大后台任务并发数 |
| 新增一台高性能机器分担转码/缩略图 | 部署第二实例,设置 IMMICH_WORKERS_EXCLUDE: 'api'(或 INCLUDE: 'microservices'),共享 Postgres/Redis/文件目录 |
| 已有 API 容器想进一步拆出微服务容器 | 参考 Jobs and Workers 的 Compose 拆分示例 |
| 集群空闲期回收算力 | 直接停掉任一 immich-server 实例,保留至少一个带 api Worker 的实例 |
| Kubernetes 环境 | 调副本数即可,确保共享存储与中间件配置一致 |
以上全部行为均可在当前仓库中复核:Worker 启动与生命周期管理见 server/src/main.ts,Worker 枚举见 server/src/enum.ts,环境变量解析与默认值见 server/src/repositories/config.repository.ts,拆分示例与健康检查适配分别见 docs/docs/administration/jobs-workers.md 和 server/bin/immich-healthcheck。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00