Immich 备份脚本实战:基于 Borg 的数据库与媒体库版本化备份方案
Immich 官方的 docs/docs/guides/template-backup-script.md 提供了一套基于 Borg 备份工具 的模板 Bash 脚本,用于将照片/视频媒体库与 PostgreSQL 数据库纳入同一套去重、版本化的快照体系中。本文完整继承该文档的初始化、定时任务与恢复流程,并结合仓库中的 docker-compose.yml、example.env 以及服务端 database-backup.service.ts 源码,解释每个参数的来龙去脉,读完你可以直接落地一套“库与库同步、快照可版本化、本地加异地双副本”的自动化备份方案。
方案总览:为什么选择 Borg
Borg 是一个功能丰富的去重归档软件,内建版本化(versioning)能力。官方文档提供的思路是:将这套模板脚本作为 cron 任务每天/每周运行一次,同时备份文件与数据库。文档特别建议:在运行脚本之前,先阅读 Borg 的 quick-start 指南,理解仓库初始化与加密选项的含义。
文档明确给出了这套方案的两点前提假设:
- 服务器外接了一块第二块硬盘,用于本地(on-site)备份;
- 通过 SSH 可访问一台远程机器,用于第三份异地(off-site)副本。
如果暂时没有异地条件,可以从模板脚本中直接删除远程相关的行,只保留本地备份;文档也提到 BorgBase 这类托管服务是异地的替代选项。
数据库备份的位置设计:先落盘到 UPLOAD_LOCATION,再随库一起进快照
这是整套脚本最关键的架构决策。数据库的导出结果先写到 Immich 上传目录(UPLOAD_LOCATION)下的 database-backup 子目录:
$UPLOAD_LOCATION/
├── database-backup/immich-database.sql # pg_dump 导出的最新数据库
├── upload/ # 用户上传的原始素材
├── profile/ # 用户头像
├── thumbs/ # 缩略图(快照中排除)
└── encoded-video/ # 转码视频(快照中排除)
随后 Borg 把 UPLOAD_LOCATION 整体纳入快照,数据库文件与媒体素材进入同一个快照、同一个时间点。这保证了每个快照里“数据库与素材始终一致”,避免了先备文件后备库(或反之)导致的引用错位问题——官方备份文档 中也强调,数据库与文件系统备份失配是恢复后出现“坏资产”的典型原因。
UPLOAD_LOCATION 正是安装时 example.env 中定义的变量(默认 ./library),并在 docker-compose.yml 中以 ${UPLOAD_LOCATION}:/data 挂载进 immich_server 容器。也就是说,你只需把 example.env 里实际配置的上传路径填进脚本即可。
与 Immich 内置自动数据库备份的关系
文档用醒目提示说明:这个脚本备份数据库 + 媒体库,与 Immich 内置的自动数据库备份工具(见 backup-and-restore.md 的 "Automatic Database Backups" 一节,默认保留最近 14 份、每天 2:00 生成,存放在 UPLOAD_LOCATION/backups)功能重叠。使用本脚本相比内置工具有两个优势:
- 存储效率更高:Borg 通过版本化+去重管理备份,而不是不断产生完整拷贝;
- 时序一致:数据库与媒体库在同一时刻备份,任意快照中二者永远同步。
从源码看,内置备份由 DatabaseBackupService 驱动:它在 ConfigInit 事件里依据管理面板配置的 backup.database 注册 cron 任务,执行时内部调用 pg_dump(见 buildPostgresLaunchArguments 与 handleBackupDatabase),完成后清理过期备份。两条路径最终都是 pg_dump 逻辑备份,因此使用本脚本后,可以安全地在管理面板关掉内置自动备份以节省空间。
前置条件(Prerequisites)
文档列出的三项前置要求:
- Borg 必须安装在服务器与远程机器上(官方文档引导安装,此处不展开外部链接);
- (可选)以非 root 用户运行:需要把该用户加入 docker 组,否则无法执行脚本中的
docker exec; - 免密 SSH:若脚本要非交互运行,需从服务器到远程机器配置 passwordless ssh;如果上一步没有加入 docker 组,请确保本步骤在 root 账号下完成。
初始化 Borg 仓库(一次性操作)
在跑定时任务前,先执行一次仓库初始化。注意 UPLOAD_LOCATION 的值要与 .env 中 Immich 实际使用的数据库/上传位置一致(即 UPLOAD_LOCATION 变量指向的宿主机路径):
UPLOAD_LOCATION="/path/to/immich/directory" # Immich database location, as set in your .env file
BACKUP_PATH="/path/to/local/backup/directory"
mkdir "$UPLOAD_LOCATION/database-backup"
borg init --encryption=none "$BACKUP_PATH/immich-borg"
## Remote set up
REMOTE_HOST="remote_host@IP"
REMOTE_BACKUP_PATH="/path/to/remote/backup/directory"
borg init --encryption=none "$REMOTE_HOST:$REMOTE_BACKUP_PATH/immich-borg"
逐行说明:
mkdir "$UPLOAD_LOCATION/database-backup":预先创建数据库导出落盘目录,后续pg_dump重定向写入它;borg init --encryption=none:初始化本地 Borg 仓库。示例使用--encryption=none(不加密)是为了简化演示;生产环境如果备份盘存在泄露风险,可考虑改用repokey/keyfile加密(这属于 Borg 自身的能力选择,文档模板默认 none);- 远程同理,通过
user@host:path语法在远端初始化第二个仓库,形成本地+异地双副本。
每日/每周执行的备份模板脚本
文档给出的完整模板如下。按说明,路径中不能包含 :、@、" 字符(否则需要转义或重命名),这是因为 Borg 的仓库位置格式 host:path::archive 依赖 : 与 :: 做分隔符:
#!/bin/sh
# Paths
UPLOAD_LOCATION="/path/to/immich/directory"
BACKUP_PATH="/path/to/local/backup/directory"
REMOTE_HOST="remote_host@IP"
REMOTE_BACKUP_PATH="/path/to/remote/backup/directory"
### Local
# Backup Immich database
docker exec -t immich_postgres pg_dump --clean --if-exists --dbname <DB_DATABASE_NAME> --username=<DB_USERNAME> > "$UPLOAD_LOCATION"/database-backup/immich-database.sql
# For deduplicating backup programs such as Borg or Restic, compressing the content can increase backup size by making it harder to deduplicate. If you are using a different program or still prefer to compress, you can use the following command instead:
# docker exec -t immich_postgres pg_dump --clean --if-exists --dbname <DB_DATABASE_NAME> --username=<DB_USERNAME> | /usr/bin/gzip --rsyncable > "$UPLOAD_LOCATION"/database-backup/immich-database.sql.gz
### Append to local Borg repository
borg create "$BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/
borg prune --keep-weekly=4 --keep-monthly=3 "$BACKUP_PATH"/immich-borg
borg compact "$BACKUP_PATH"/immich-borg
### Append to remote Borg repository
borg create "$REMOTE_HOST:$REMOTE_BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/
borg prune --keep-monthly=3 --keep-weekly=4 "$REMOTE_HOST:$REMOTE_BACKUP_PATH"/immich-borg
borg compact "$REMOTE_HOST:$REMOTE_BACKUP_PATH"/immich-borg
把这段脚本放入你的 crontab 即可(文档未规定具体调度频率,daily/weekly 均可,按数据增长速度取舍)。
数据库导出:pg_dump 的参数细节
模板中的核心一行:
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname <DB_DATABASE_NAME> --username=<DB_USERNAME> \
> "$UPLOAD_LOCATION"/database-backup/immich-database.sql
immich_postgres是 docker-compose.yml 中database服务固定的container_name,脚本因此可以直接 exec 进容器执行pg_dump;<DB_DATABASE_NAME>与<DB_USERNAME>对应.env中的DB_DATABASE_NAME(默认immich)与DB_USERNAME(默认postgres),见 example.env;--clean让 dump 内含DROP语句、--if-exists避免 DROP 时报错,二者配合使导出的 SQL 可在全新库上直接回放;- 文档注释特别说明:对 Borg/Restic 这类去重备份程序,压缩反而会增大备份体积(压缩使相邻快照字节序列趋同困难、削弱去重效果),因此默认不压缩;如果你用别的备份工具、或坚持要压缩,则改用注释里给出的
gzip --rsyncable管道版本。
从源码结构看,Immich 内置备份走的是容器内 /usr/lib/postgresql/<version>/bin/pg_dump(见 database-backup.service.ts 与对应测试快照 database-backup.service.spec.ts),与脚本里 docker exec 的 pg_dump 是同一工具的不同入口,恢复方式因此完全通用。
Borg create / prune / compact 三件套
每条快照流程都是固定的三步:
| 命令 | 作用 |
|---|---|
borg create <repo>::{now} <path> --exclude ... |
创建一个以当前时间命名(Borg 内置 {now} 占位符)的新快照 |
borg prune --keep-weekly=4 --keep-monthly=3 <repo> |
按保留策略裁剪旧快照:保留最近 4 份周快照 + 3 份月快照 |
borg compact <repo> |
压缩 Borg 仓库,回收碎片空间 |
两个 --exclude 值得注意:排除了 $UPLOAD_LOCATION/thumbs/ 与 $UPLOAD_LOCATION/encoded-video/。这与 官方备份文档的 Filesystem 一节 的存储布局一致——thumbs(缩略图)和 encoded-video(转码视频)都属于可再生内容,真正关键的是 upload/library(原始素材)、profile(头像)和数据库本身。排除二者可以显著减小快照体积;恢复后如需重建,需要重跑转码与缩略图生成任务(文档原文提示了这一点)。--keep-weekly=4 --keep-monthly=3 只是模板值,可按保留策略自行调整。
恢复(Restoring)
恢复走 borg mount:把仓库挂载为目录,每个快照是挂载点下的一个子目录,按需取文件,最后卸载。
从本地备份恢复:
BACKUP_PATH="/path/to/local/backup/directory"
mkdir /tmp/immich-mountpoint
borg mount "$BACKUP_PATH"/immich-borg /tmp/immich-mountpoint
cd /tmp/immich-mountpoint
从远程备份恢复:
REMOTE_HOST="remote_host@IP"
REMOTE_BACKUP_PATH="/path/to/remote/backup/directory"
mkdir /tmp/immich-mountpoint
borg mount "$REMOTE_HOST:$REMOTE_BACKUP_PATH"/immich-borg /tmp/immich-mountpoint
cd /tmp/immich-mountpoint
在 /tmp/immich-mountpoint 下可以看到各个时间点的快照子目录。选取所需快照后,把 upload、profile、database-backup 等目录内容复制回新的 UPLOAD_LOCATION(注意:如果你的 profile/ 等目录曾拆分到别的存储设备,恢复路径要按实际部署调整)。数据库部分,把快照里的 database-backup/immich-database.sql 通过 psql 灌回新库即可,官方文档给出的灌库命令(带 search_path 修正与单事务保护)可参考 backup-and-restore.md 的命令行恢复小节。全部完成后执行 borg umount /tmp/immich-mountpoint 卸载仓库。
要点回顾与适用边界
- 这套方案适合已用 Docker Compose 部署 Immich、希望一条 cron 脚本同时覆盖数据库与媒体库的场景;脚本强依赖
docker exec immich_postgres,因此服务器必须能访问 Docker 守护进程(root 或将运行用户加入 docker 组); - 模板假设路径中无
:、@、"字符;{now}时间戳、--keep-weekly=4 --keep-monthly=3保留策略、以及本地/异地双写都是可按需修改的参数; - 与内置备份二选一即可:使用本脚本后建议在管理面板关闭自动数据库备份,避免在
UPLOAD_LOCATION/backups中持续累积冗余拷贝; - 本文所有事实均出自 template-backup-script.md 原文及其引用的 backup-and-restore.md、example.env、docker-compose.yml 与 database-backup.service.ts,未涉及仓库之外的假设。
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 StartedRust0626
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