首页
/ Immich 备份脚本实战:基于 Borg 的数据库与媒体库版本化备份方案

Immich 备份脚本实战:基于 Borg 的数据库与媒体库版本化备份方案

2026-09-04 16:46:33作者:霍妲思

Immich 官方的 docs/docs/guides/template-backup-script.md 提供了一套基于 Borg 备份工具 的模板 Bash 脚本,用于将照片/视频媒体库与 PostgreSQL 数据库纳入同一套去重、版本化的快照体系中。本文完整继承该文档的初始化、定时任务与恢复流程,并结合仓库中的 docker-compose.ymlexample.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(见 buildPostgresLaunchArgumentshandleBackupDatabase),完成后清理过期备份。两条路径最终都是 pg_dump 逻辑备份,因此使用本脚本后,可以安全地在管理面板关掉内置自动备份以节省空间

前置条件(Prerequisites)

文档列出的三项前置要求:

  1. Borg 必须安装在服务器与远程机器上(官方文档引导安装,此处不展开外部链接);
  2. (可选)以非 root 用户运行:需要把该用户加入 docker 组,否则无法执行脚本中的 docker exec
  3. 免密 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_postgresdocker-compose.ymldatabase 服务固定的 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 execpg_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 下可以看到各个时间点的快照子目录。选取所需快照后,把 uploadprofiledatabase-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.mdexample.envdocker-compose.ymldatabase-backup.service.ts,未涉及仓库之外的假设。
登录后查看全文
热门项目推荐
相关项目推荐