SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

Immich VPS 備份與還原完整指南

了解 Immich 備份必須包含原始檔案、Postgres SQL 傾印與 stack 設定,為何複製資料目錄不算備份,以及還原順序錯誤會讓時間軸為空。

Immich 備份必須包含的內容

Immich 備份必須在同一時間擷取 3 項內容。UPLOAD_LOCATION 下的原始檔案。Postgres 資料庫的 SQL 傾印。描述整個 stack 的 .envdocker-compose.yml。還原時,必須先在 Immich server 停止的狀態下,將該傾印匯入全新的資料庫,之後才能啟動 stack 的其餘服務。順序錯誤時,最後可能得到一個運作正常的 Immich,但時間軸為空,底層磁碟卻已經存滿資料。

這種拆分很重要,因為 Immich 將狀態分別儲存在 2 個彼此不相通的位置。Postgres 儲存所有相簿、所有臉部群組、所有分享連結、所有使用者帳號與 API key,以及每個資產的儲存路徑。檔案系統儲存影像資料。只還原檔案而未還原資料庫時,Immich 不會顯示任何內容。只還原資料庫而未還原檔案時,每個資產都會開啟為損毀的影像。

這裡的命令以 Immich v3.1.0 為準,這是 2026 年 8 月初的目前版本。該專案發布速度很快,文件中的備份程序也已多次變更,因此在複製任何內容前,請先確認實際執行的版本。如果 stack 尚未啟動,請先參閱 Immich 安裝指南,再返回這裡。

了解路徑的實際指向

.env 中的兩個變數會決定本頁的所有內容。UPLOAD_LOCATION 是 Immich 寫入所有媒體檔案的父目錄。DB_DATA_LOCATION 是 Postgres 資料目錄。

原始的 example.env 會設定 UPLOAD_LOCATION=./library。這個預設值容易造成混淆,因為 Immich 接著會在其中建立名為 library 的資料夾。您的原始檔案最後會位於 ./library/library。請改用絕對路徑,讓備份指令碼不會依賴您執行指令時所在的目錄。

UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0

Immich 會在 UPLOAD_LOCATION 中建立數個資料夾。其中 3 個資料夾存放任何工作都無法重建的資料:

  • library:依您的儲存範本排列的原始檔案
  • upload:尚未移入範本配置的原始檔案,以及正在上傳的檔案
  • profile:使用者個人資料圖片

遺失 library 後,照片就會消失。Immich 不會在其他位置保留原始檔案的第二份副本。

為何複製 Postgres 資料目錄不算備份

DB_DATA_LOCATION 看起來很容易處理。它是一個目錄,rsync 可以複製它,而且複製完成時不會顯示錯誤。但這仍然不算備份,原因有兩個,而且都能觀察到失敗結果。

第一個原因是資料不一致。Postgres 會先將每項變更寫入預寫日誌(WAL),之後在 checkpoint 時才套用到資料表檔案。因此,磁碟上的檔案在任何時間點都可能處於寫入過程中。如果採用需要 4 分鐘的逐一複製,第一個檔案在 02:00 讀取,最後一個檔案在 02:04 讀取。這兩個檔案不屬於同一筆交易。使用複製結果啟動 Postgres 時,可能會在啟動階段因 PANIC: could not locate a valid checkpoint record 拒絕啟動,也可能成功啟動,直到第一次讀取損毀頁面時才因 invalid page in block 1234 of relation base/16384/... 結束。這兩種情況都無法從該複製結果復原。

即使先停止所有服務,第二個原因仍然存在。Postgres 資料目錄與寫入該目錄的確切執行檔版本綁定。Immich 目前以 digest 固定其資料庫映像檔,值為 ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0。這是 Postgres 14,並且內含 2 個編譯完成的向量搜尋擴充功能。由該版本寫入的資料目錄無法在不同的 Postgres major version 下開啟,也無法在擴充功能版本不同的版本下開啟。復原主機必須完全重現該映像檔。SQL dump 則不受此限制:它是文字格式,任何相容的伺服器都能重新執行其中的內容。

pg_dump 直接避開資料不一致問題。它會在單一 MVCC(多版本並行控制)snapshot 中讀取整個資料庫,因此即使其他寫入作業持續進行,也能讀取資料庫在某一個時間點的完整狀態。這就是不必停止 Postgres 也能建立 dump 的原因。

可從備份中排除的內容

以下內容都能重新產生,因此可以略過:

  • thumbs:預覽圖與縮圖
  • encoded-video:轉碼後的影片
  • DB_DATA_LOCATION:從 dump 重新建立
  • model-cache Docker volume:機器學習模型,會在需要時重新下載

略過這些內容是一項取捨,不是毫無代價的選擇。大型媒體庫重新建立縮圖與轉碼內容,可能會讓小型 VPS 持續耗用數小時的 CPU;在此期間,時間軸會一直顯示灰色佔位符。你可以前往 Administration > Jobs,將「Generate Thumbnails」與「Transcode Videos」設定為只處理缺少的資產,然後重新執行。如果備份目標有足夠空間,請納入這些內容,以免等待重建。如果接近儲存空間上限,則可排除這些內容,但必須預留重建時間。估算 Immich 媒體庫大小說明這些資料夾相對於原始檔案會增長到多大。

另外還有一個值得了解的資料夾。UPLOAD_LOCATION/backups 儲存 Immich 自動產生的資料庫 dump;系統每天 02:00 寫入,並保留最後 14 份,可在 Administration > Settings > Backup 中設定。這些 dump 不會額外消耗你的資源,而且確實很有用。不過,它們與所保護的媒體庫位於同一顆磁碟,因此只能協助處理錯誤的遷移,無法應對伺服器故障。無論如何,仍應自行執行 dump,因為你手動觸發的 dump 會與相應的檔案 snapshot 在同一時間建立。

取得資料庫傾印

docker exec -t immich_postgres pg_dump --clean --if-exists \
  --dbname=immich --username=postgres \
  | gzip > /srv/immich/backup/immich.sql.gz

如果你曾修改 immichpostgres,請將它們替換為你的 DB_DATABASE_NAMEDB_USERNAME--clean --if-exists 會在每個 CREATE 前加上 DROP ... IF EXISTS,讓傾印內容能匯入已經包含物件的資料庫,而不會在遇到第一個物件時停止。

接下來是會讓備份指令碼悄悄失效的細節。該指令是管線,而 shell 會回報管線中最後一個指令的結束狀態。如果 pg_dump 因密碼錯誤或容器未執行而失敗,gzip 會收到空的資料流,寫出格式完全有效的 gzip 檔案,並以 0 結束。你的指令碼會記錄成功,但實際上只得到一個 20 位元組的備份檔案。請在每個備份指令碼開頭加入 pipefail

#!/usr/bin/env bash
set -euo pipefail

接著檢查結果,不要只相信結束碼:

ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3

正常傾印的第一行是 -- PostgreSQL database dump。無論指令碼回報什麼,只有幾百位元組的檔案都表示傾印失敗。

在傾印檔案旁記錄產生該檔案的 build:

docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txt

不要依賴 .env。預設檔案會設定 IMMICH_VERSION=v3,這是會隨著每個 3.x 版本更新的浮動標籤,因此無法告訴你實際產生傾印的 build。也請在 .env 中固定使用確切的標籤。

暫停伺服器,再使用 restic 建立快照

UPLOAD_LOCATION 下的檔案在 Immich 執行期間並非不可變更。伺服器會寫入新的上傳檔案,儲存範本工作也會在目錄之間移動檔案。如果備份工具在檔案寫入到一半時讀取該檔案,就會將當時讀到的位元組當成完整檔案儲存,而且不會回報任何錯誤。請在整個執行期間停止伺服器容器:

docker stop immich_server

請讓 immich_postgres 持續執行,因為傾印程序需要它。直到再次啟動伺服器前,Web 介面與行動應用程式都會離線;對家用伺服器而言,如果在 03:00 執行,通常不會造成問題。

restic 適合這個用途,因為它會先執行去重複與加密,再將資料傳出伺服器。請將它指向不在此伺服器上的儲存庫:

export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init

物件儲存的方式相同。如果希望備份完全離開自有硬體,物件儲存是更好的選擇:

export RESTIC_REPOSITORY=s3:https://s3.example.com/immich-backup
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
restic init

該端點可以是您在第二台機器上自行執行的 MinIO bucket,也可以是任何與 S3 相容的供應商。將儲存庫放在與媒體庫相同的磁碟上,只能防止誤刪,無法防範其他問題。

接著建立快照,只列出真正重要的內容:

restic backup \
  /srv/immich/backup/immich.sql.gz \
  /srv/immich/backup/immich-version.txt \
  /srv/immich/data/library \
  /srv/immich/data/upload \
  /srv/immich/data/profile \
  /srv/immich/.env \
  /srv/immich/docker-compose.yml
docker start immich_server

restic 每次執行都會讀取整個樹狀目錄,但只會上傳之前未見過的區塊。因此,第一個快照會傳輸整個媒體庫;之後的每個快照只會傳輸當天新增的照片。

保留政策,以及必須存放在其他位置的金鑰

restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12

forget 會從索引中移除快照。--prune 則會刪除那些快照曾是其最後參照的資料。執行 forget 時若未加上 --prune,儲存費用就不會下降。

結構檢查成本低,因此每週執行一次:

restic check

這會驗證儲存庫中繼資料是否一致,但不會讀取資料。每月重新讀取一部分資料,並與記錄的雜湊值比對:

restic check --read-data-subset=5%

只有這項檢查能偵測儲存後端的靜默損毀,因為它會下載實際區塊並重新計算其 checksum。對相片資料庫執行完整的 --read-data,代表要下載整個儲存庫。若使用按量計費的物件儲存,這會產生實際費用。因此,實務上通常採用輪替檢查的子集。

接下來是許多人會略過的部分。restic 儲存庫密碼無法復原。 沒有重設機制,也無法提交支援工單。如果唯一的副本位於你要進行還原的伺服器上的 /root/.restic-password,備份內容就只是一堆無法解密的資料。物件儲存存取金鑰,以及 .env 中的 DB_PASSWORD 也是如此。請將所有這些資訊存放在不依賴這台機器持續運作的位置:列印後放在抽屜中,或儲存在使用不同硬體執行的密碼管理器中。如果該管理器也是自架服務,也必須採取相同的處理方式;而 備份 Vaultwarden 則是另一項工作。

依照正確順序還原 Immich

還原順序會決定備份是否能還原完整時間軸。請在新主機上依照以下順序操作。

先還原設定。 設定會指定要執行的版本,以及各路徑的對應位置。

restic restore latest --target /restore \
  --include /srv/immich/.env \
  --include /srv/immich/docker-compose.yml \
  --include /srv/immich/backup

在啟動任何服務前固定版本。 讀取 immich-version.txt,在 .env 中將 IMMICH_VERSION 設為完全相同的 tag,暫時不要使用最新版本。Immich 不支援降級,即使只是不同的修補版本也不支援。因此,若較新的伺服器先使用較舊的 dump 啟動並執行 migration,就無法復原。

還原媒體檔案。

restic restore latest --target /restore --include /srv/immich/data

接著移動 libraryuploadprofile,讓它們直接位於本機 UPLOAD_LOCATION 所指向的目錄中。主機上的路徑可以不同,因為 compose 檔案會將該目錄繫結至容器內的固定路徑。但目錄內的結構不能變更。

單獨啟動資料庫。DB_DATA_LOCATION 保持空白,使 Postgres 初始化新的 cluster。

cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgres

完成首次設定後,pg_isready 會輸出 accepting connections,這需要幾秒鐘。docker compose create 會建立所有容器,但不會啟動容器。這正是此步驟的目的:Immich server 此時不能執行。若伺服器在空資料庫上啟動,就會執行 migration、建立新的 schema,並要求你建立新的 admin 帳號。之後你會在執行中的應用程式下方重新匯入 dump。

重新匯入 dump。

gunzip --stdout /restore/srv/immich/backup/immich.sql.gz \
  | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
  | docker exec -i immich_postgres psql --dbname=immich --username=postgres \
      --single-transaction --set ON_ERROR_STOP=on

其中有兩項設定會實際影響還原結果。sed 的存在,是因為 pg_dump 會在輸出中寫入空的 search_path,作為安全措施,避免 dump 中未限定 schema 的名稱解析到非預期的 schema。Immich 的向量搜尋類型位於 public。因此,若 search path 為空,還原程序遇到第一個使用 vector 類型宣告的欄位時,psql 會因 ERROR: type "vector" does not exist 而停止。將 public 加回 search path 即可修正。

--single-transaction --set ON_ERROR_STOP=on 會將整個還原程序包在單一 transaction 中,並在第一個錯誤發生時中止。這樣還原結果只有兩種:完整還原或資料庫維持未變更。若沒有這項設定,中途失敗會留下仍能啟動並接受登入的資料庫,但其中缺少數量不明的相簿,直到數週後才會發現。

現在啟動所有服務。

docker compose up -d
docker compose ps
docker logs -f immich_server

等待出現類似 Immich Server is listening on 的啟動訊息,然後開放 2283 埠並使用舊的憑證登入,因為使用者帳號已隨 dump 還原。若登入頁面改為要求建立第一個 admin 帳號,表示資料庫未成功還原。請停止操作,重新檢查 psql 輸出。

官方還原說明的開頭是 docker compose down -v,這裡有一項警告。-v 會移除具名 volume。在預設 compose 檔案中,UPLOAD_LOCATIONDB_DATA_LOCATION 是 bind mount,因此不會受影響。若你將其中任一項改為具名 volume,該命令會刪除照片。執行前請先閱讀 compose 檔案。

還原後時間軸為何是空的

時間軸是根據資料庫資料列建立的。Immich 啟動時不會掃描 upload/ 來重新尋找相片,因為沒有資料列的檔案沒有擁有者、日期或相簿。因此,最常見的錯誤還原情況是檔案已還原,但資料庫遺失。Immich 會啟動並建立空的 schema,讓你取得可運作的執行個體,但其中沒有任何內容,而磁碟上仍存放著所有相片。資料沒有遺失,但也不會顯示。修正方式是在伺服器停止時重新套用 dump,方式與上述完全相同。

第二種情況較不明顯。資料庫已還原,時間軸也出現項目,但每個資產都無法開啟。這表示資料列指向容器無法存取的檔案,通常是因為執行 restic restore --target /restore 後,沒有人將 libraryuploadprofile 移到正確位置,導致它們多了一層目錄。請從容器內部檢查,不要猜測:

docker exec immich_server ls /data

預設的 compose 檔案會將 UPLOAD_LOCATION 掛載至 /data,因此該清單應顯示 libraryuploadprofile。如果顯示空目錄或多出的 srv 目錄,表示 bind mount 指向錯誤的層級,而資料列本身沒有問題。

備份與還原之間的版本一致性

Immich 經常發布新版本,資料庫 schema 也會隨之變更,因此 dump 會包含建立該 dump 的伺服器所使用的 schema。

將較舊的 dump 還原至較新的伺服器通常可行,因為伺服器啟動時會套用尚未執行的 migration,逐步更新 schema。這條路徑會依照版本發布順序進行測試。一次跨越數個 major version 時,最容易發生問題;專案會將破壞性變更保留在 major release,並記錄於 changelog 中。

將較新的 dump 還原至較舊的伺服器則完全不可行。dump 會包含較舊程式碼不認得的資料表與欄位,而 Immich 也表示不支援降級,即使是在 patch release 之間也一樣。沒有可用的 rollback command。

因此,安全的還原方式看似單調。執行建立該 dump 的完全相同版本,還原資料,登入後確認時間軸完整,再進行升級。每次只升級一個 release,逐次更新 IMMICH_VERSION,並在每次更新後執行 docker compose pull && docker compose up -d。保留一週的 dump 也很有幫助:如果最新的 dump 是在升級失敗期間建立的,昨天的 dump 仍會留在 repository 中。

每月驗證備份

從未還原過的備份只是猜測。每月將備份還原到一次性使用的 instance,並開啟一張照片查看。這項演練約需 20 分鐘,也是唯一能將本頁其餘內容轉化為復原計畫的步驟。

restic snapshots
restic stats latest

snapshots 應列出昨晚的執行結果。stats latest 應顯示接近相片庫的大小,而不是只有幾 MB。

將備份還原到暫存目錄,最好使用備用主機:

restic restore latest --target /tmp/immich-drill

從還原後的集合複製 docker-compose.yml.env,然後在副本中修改 3 項設定。將 UPLOAD_LOCATIONDB_DATA_LOCATION 指向 /tmp/immich-drill 下的目錄。將 web port 發布到其他位置,使用 12283:2283 取代 2283:2283。刪除 container_name: 行,因為原始 compose 檔案會硬編碼 immich_server 等名稱。如此一來,同一主機上的第二個 stack 會與第一個衝突,Docker 也會拒絕建立它。

執行上方的還原程序:先只還原資料庫,再重播 dump,最後執行 docker compose up -d。接著進行以下 4 項檢查,確認還原確實成功。

  1. 使用演練前的密碼登入。帳號可以正常使用,表示 dump 已成功還原。
  2. 開啟時間軸並捲動到最早的月份。整個日期範圍內都有資產,表示所有資料列都已還原,而不只是最近的資料。
  3. 以完整尺寸開啟一張照片,並下載原始檔案。
  4. 使用 sha256sum,將該檔案與正式相片庫中的相同檔案比較。雜湊值一致,表示檔案內容已完整通過 restic 的往返流程。

接著在演練目錄中使用 docker compose down -v 清除演練環境,並刪除 /tmp/immich-drill。將日期記錄在你會看到的地方,因為這項工作的價值完全取決於下個月再次執行。如果你仍在決定要採用哪個相片伺服器,PhotoPrism 與 Immich 的比較會說明兩者在這項能力上的差異。

FAQ

我是否必須停止 Immich 才能備份?

停止 immich_server,讓 immich_postgres 繼續執行。資料庫不需要暫停,因為 pg_dump 會在單一 MVCC snapshot 內讀取,因此無論其他程序如何寫入,看到的都是同一個一致時間點。需要停止的是檔案服務:伺服器會寫入新的上傳檔案,而 storage template job 會在目錄之間移動檔案。因此,備份工具可能在檔案寫入到一半時讀取,並在沒有任何錯誤的情況下儲存截斷的副本。在 snapshot 前執行 docker stop immich_server,並在完成後執行 docker start immich_server,即可排除這項競態。

我可以直接複製 Postgres data folder,而不執行 pg_dump 嗎?

不行。對執行中的 data directory 進行滾動複製,會在不同時間點讀取不同檔案,因此結果不是單一一致狀態。Postgres 啟動時會因 PANIC: could not locate a valid checkpoint record 拒絕使用該副本,或稍後因頁面損壞而失敗。即使在所有服務停止後進行複製,該副本也只適用於完全相同的資料庫組建:Immich 固定使用 Postgres 14 image,以及特定版本的 vector-search extension,該目錄無法在其他環境中開啟。SQL dump 是純文字,可匯入任何相容的伺服器。

為什麼還原後 Immich timeline 是空的?

因為 timeline 是由資料庫資料列建立,而你只還原了檔案,沒有還原資料庫。Immich 不會掃描 upload/ 來重新尋找相片,因此沒有對應資料列的檔案會維持不可見。相片本身沒有受損。停止伺服器,將 dump 匯入全新初始化的 Postgres,然後啟動整個 stack。如果 timeline 已完整,但每張相片都無法開啟,問題則相反:libraryuploadprofile 並不直接位於繫結至 container 的目錄內。使用 docker exec immich_server ls /data 檢查。

Immich 的哪些資料夾可以略過備份?

thumbsencoded-video 可由原始檔案重新產生,而 DB_DATA_LOCATION 可由 dump 重建,因此這些資料夾都不必納入備份集合。略過這些項目,是把時間成本放在還原後,而不是把儲存空間成本放在還原前,因為大型資料庫的預覽圖與轉碼檔重建可能需要數小時的 CPU 時間;這項工作會從 Administration > Jobs 執行,並針對遺失的資產處理。絕不能略過的是 libraryuploadprofile,因為它們保存每個原始檔案的唯一副本。

我可以將 Immich dump 還原到較新的版本嗎?

通常可以,因為伺服器會在啟動時套用待處理的 migrations,逐步更新 schema。反向操作會失敗:Immich 不支援降級,即使是 patch release 之間也不支援,因此無法將較新 release 的 dump 載入較舊的伺服器。使用固定為產生該 dump 之 release 的 IMMICH_VERSION 進行還原,確認 timeline 完整後再升級。使用 docker inspect --format '{{.Config.Image}}' immich_server 將版本記錄在每個 dump 旁,因為預設的 IMMICH_VERSION=v3 是 floating tag,無法提供任何版本資訊。