n8n 搬家實戰:從 Zeabur 匯出資料庫,搬到自己的環境
搬 n8n 最快的方法是整包搬資料庫:workflow(工作流程)、credential(連線憑證)、帳號與執行紀錄一口氣搬完,不用一個一個匯出匯入,完全無痛。關鍵是保留原本的 ENV(環境變數,尤其是 Encryption Key),在新環境準備好 PostgreSQL 與同版本的 n8n,先還原資料庫,再啟動 n8n。最後搭配一支 Password Reset Script 解決登入,原始碼附在文末。
這篇是筆者把一個 28 個 workflow 的 n8n 從 Zeabur 搬出來的實際紀錄。以下用 Docker Compose 示範操作,再介紹筆者公司的 GKE 環境。存在資料庫以外的檔案(例如存在磁碟上的圖片、文件)要另外搬,資料庫備份帶不走。
整個搬家只有兩樣東西要從舊家帶走:資料庫備份與 Encryption Key;新環境照 1、2、3 的順序做,先還原再啟動 n8n。
關鍵一:在 Zeabur 後台手動觸發 PostgreSQL 備份
備份要進 PostgreSQL 服務操作,不是 n8n 服務。Zeabur 支援資料庫線上備份,備份時不用停止 PostgreSQL。
- 進 Zeabur 專案,點 PostgreSQL 那個服務。
- 切到 Backup 分頁,按 Backup。
- 等幾分鐘,確認列表出現新的備份。
試搬可以不停舊 n8n,正式切換則要先停。 正式切換當天,先暫停舊 n8n,再備份一次,避免備份後還有新資料寫入。若有 worker(負責執行工作流程的程序),也要確認舊環境已停止執行工作。
Backup 功能需要 Dev Plan 以上。 沒有的話,可以到 PostgreSQL 的 Instruction 分頁取得連線字串,自己跑 pg_dump;但那是另一條備份路線,後文的抽取指令是針對 Zeabur 備份功能產生的格式。參考 Zeabur 備份文件。
關鍵二:下載備份,確認格式與資料庫名稱
備份完成後,在同一個 Backup 分頁下載檔案。筆者下載到的檔名是 backup-xxxxxxxx.sql.gz。
不要只看副檔名判斷格式。 筆者拿到的檔案其實是 zip,直接用 gunzip 會失敗。先用 file 確認;結果是 Zip archive data,再用下列方式解開:
file backup-xxxxxxxx.sql.gz # Zip archive data
unzip backup-xxxxxxxx.sql.gz -d zeabur-backup
ls zeabur-backup/data # _manifest.json data.sql
| 檔案 | 裡面是什麼 |
|---|---|
data/_manifest.json |
備份資訊,可以看到來源 PostgreSQL 的主版本,筆者的是 17 |
data/data.sql |
用 pg_dumpall 備份的純文字 SQL,筆者的約 210 MB |
這份備份包含多個資料庫,還原前要找出 n8n 用的是哪一個。 pg_dumpall 是一次匯出 PostgreSQL 服務內所有資料庫與角色設定的工具。筆者的備份包含角色(CREATE ROLE)、postgres、template1,以及 n8n 使用的資料庫。
Zeabur 上的資料庫不一定叫 n8n。 筆者的叫 zeabur。以下指令會列出備份中的資料庫名稱,以及來源 PostgreSQL 版本:
grep -n '^-- Database "' zeabur-backup/data/data.sql
grep -n 'Dumped from database version' zeabur-backup/data/data.sql | head -1
備份檔含個資與憑證,請當成機密保存。 裡面有使用者的 Email 和加密過的 credential,不要 commit 進 git,也不要貼給 AI 看內容。筆者在 repo 的 .gitignore 擋掉 *.sql、*.dump、backups/。
關鍵三:保存環境變數,保留原本的 Encryption Key
先把舊環境的設定保存下來,再決定哪些要搬。到 Zeabur 的 n8n 服務 → Variables,把環境變數複製到本機不會進 git 的檔案,例如 secrets/zeabur-n8n.env,權限設為 600(只有檔案擁有者能讀寫)。
N8N_ENCRYPTION_KEY 必須保留原值。 Encryption Key 是解開 credential 的金鑰。API key、OAuth token、資料庫密碼等憑證,都是加密後才存進資料庫。
- 沿用同一把金鑰:新環境才能解開原本的 credential。
- 使用不同金鑰:原本的 credential 會無法解密,應先找回並設定原金鑰。
- 新環境未設定金鑰:n8n 首次啟動會自行產生一把新的,結果和使用不同金鑰一樣。
- 有 worker:main(主程序)與 worker 要用同一把金鑰。
備份資料庫不等於備份金鑰,兩者都要保存。
其他變數要依用途處理,不能整包套到新環境。
| 類型 | 例子 | 怎麼處理 |
|---|---|---|
| 必帶 | N8N_ENCRYPTION_KEY |
原值照搬 |
| 平台專屬,要換成新環境的值 | DB_POSTGRESDB_*、N8N_HOST、WEBHOOK_URL、PASSWORD、Redis 與 N8N_RUNNERS_* |
依新環境重新設定;新環境沒有的元件,不要沿用舊連線設定 |
| 一般設定,可以照搬 | GENERIC_TIMEZONE、NODE_ENV、NODES_EXCLUDE、N8N_DIAGNOSTICS_ENABLED |
確認仍符合需求後複製 |
也要記下 n8n 的版本。 到 n8n 服務查看 image tag(容器映像的版本標籤),筆者的是 2.8.3。新環境先用同一版,搬完並確認穩定後再升級。
準備新環境:先對版本,再確認功能需求
新環境只需要 PostgreSQL 加上一個 n8n。以下版本對應筆者的來源環境,讀者要先核對自己的版本。
| 元件 | 筆者用的版本 | 為什麼 |
|---|---|---|
| PostgreSQL | postgres:17 |
和 Zeabur 來源同主版本(17.11),降低還原時的相容性風險 |
| n8n | docker.n8n.io/n8nio/n8n:2.8.3 |
n8n 官方 image,和 Zeabur 上的版本一樣 |
搬遷時先維持版本,別同時升級。 版本只能同版或更新,不要降版:資料庫裡的資料表結構已經是這個版本的樣子,舊版 n8n 看不懂。
還原完成前,不要啟動 n8n。 n8n 首次啟動會在空資料庫建表,之後再還原可能與既有資料表衝突。順序固定是「先還原,再啟動」。
能不能簡化成單一 n8n,要看 workflow 用了什麼功能。 Zeabur 的 n8n v2 模板還有 worker、Redis(佇列模式用來傳遞工作的服務)與 task runner(執行 Code node 程式碼的程序)。搬之前先確認是否使用 Python Code node,以及是否有存在檔案系統的 binary 資料(圖片、文件等檔案);相關限制見「踩過的坑」。
小專案建議:用 Docker Compose,編輯器不要曝露在公開網路
Docker Compose 可以用一份設定檔管理 PostgreSQL 與 n8n,小專案用它就能把複雜度降到最低。以下假設機器已安裝 Docker 與 Docker Compose。
先建立 docker-compose.yml,此時還不要啟動全部服務:
services:
postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: n8n
volumes:
- pgdata:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/n8nio/n8n:2.8.3
restart: unless-stopped
depends_on: [postgres]
ports:
- "127.0.0.1:5678:5678" # 只綁本機,不對外開放
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY} # 從 Zeabur 原值照搬
GENERIC_TIMEZONE: Asia/Taipei
volumes:
- n8ndata:/home/node/.n8n
volumes:
pgdata:
n8ndata:
環境變數放在同目錄的 .env。 DB_PASSWORD 是新環境 PostgreSQL 要使用的密碼;N8N_ENCRYPTION_KEY 則必須是 Zeabur 的原值。記得把 .env 加進 .gitignore。
127.0.0.1 表示只有執行 Docker 的那台機器能連進來。 n8n 編輯器拿得到所有 credential,不應直接暴露在公開網路。從別台電腦連線時,用 SSH tunnel(透過 SSH 建立的連線通道):ssh -L 5678:localhost:5678 你的機器,其中「你的機器」換成實際的 SSH 連線目標。
只裝 Tailscale 這類私有網路還不夠。 服務只綁本機時,安裝 Tailscale 不會讓它自動接受遠端連線,還要另外設定入口;本文沒有提供這部分。
外部 webhook 要另外設計入口。 Webhook 是讓外部服務呼叫、觸發 workflow 的網址。有這類需求時,只另外開放 /webhook 路徑;上面的 Compose 本身沒有公開入口。
還原資料庫、核對資料,再啟動 n8n
全程五步,在 docker-compose.yml 所在目錄執行,並確認備份的相對路徑與範例一致。
這套流程以全新的空資料庫為前提。 如果曾啟動過 n8n,或還原到一半失敗,不要直接重跑整份 SQL,先確認資料庫狀態。
-
只啟動 PostgreSQL。
docker compose up -d postgres -
只取出 n8n 使用的資料庫內容。 備份用
\connect <資料庫名>分段。下面的指令取出zeabur那段,再移除舊角色root的擁有者設定,因為新環境沒有這個角色。zeabur和root是筆者環境的值,要換成你在關鍵二查到的名稱。awk '/^\\connect /{p=($2=="zeabur"); next} p' zeabur-backup/data/data.sql \ | grep -v 'OWNER TO root' > n8n.sql -
還原到新資料庫,遇到錯誤就停止。 新資料庫叫
n8n,可以和來源的zeabur不同。有錯誤時先處理原因,不要接著啟動 n8n。docker compose exec -T postgres psql -U n8n -d n8n -v ON_ERROR_STOP=1 < n8n.sql -
核對 workflow、credential 與使用者筆數,和舊 n8n 畫面上的數字比較:
docker compose exec postgres psql -U n8n -d n8n -c \ 'select (select count(*) from workflow_entity) as workflows, (select count(*) from credentials_entity) as credentials, (select count(*) from "user") as users;' -
先處理排程,再啟動 n8n。 試搬時舊 n8n 還在跑,先在新環境記下啟用清單、再全部停用,避免兩邊同時執行:
docker compose run --rm n8n list:workflow --active=true --onlyId > active-workflows.txt docker compose run --rm n8n update:workflow --all --active=false docker compose up -d n8n docker compose logs -f n8n # 看到 n8n ready on ...正式切換時,先確認舊 n8n 已停止,再照
active-workflows.txt把 workflow 開回來。
搬遷完成要以實際功能驗證為準。 除了筆數一致,也要確認能登入、credential 能連線、workflow 能執行。網址改了的話,外部服務設定的 webhook 網址也要更新並測試。
筆者的結果:資料完整,credential 不用重設。 58 張表的筆數與備份完全一致,28 個 workflow、15 個 credential、13 個使用者一個不少。先在自己電腦照這五步演練一次,再搬到正式環境;正式切換時要用停止舊環境後取得的最新備份。
踩過的坑
| 坑 | 發生什麼事 | 怎麼避免 |
|---|---|---|
| 排程兩邊一起跑 | 新 n8n 啟動後,備份中原本啟用的排程 workflow 也會開始運作。舊環境還在跑,就可能重複寫入資料 | 試搬時照前一節第 5 步,啟動前先保存啟用清單並停用 workflow。正式切換時先停舊環境,再開回來 |
| 以為是 Google 登入 | 筆者其實是用 Chrome 自動填入 Email 與密碼。換網址後,密碼沒有自動帶出 | 到 passwords.google.com 用舊網址找密碼;找不到再用文末的 Password Reset Script |
| 登入帳號不是自己想的那個 | Email 打錯,重設密碼時顯示 UPDATE 0 |
先查詢使用者:select email, "roleSlug" from "user";。global:owner 是擁有者角色,用它登入看得最完整 |
| Zeabur 備份不是 gzip | 筆者下載的副檔名是 .sql.gz,實際格式是 zip |
先用 file 確認,再決定是否用 unzip |
| binary 資料存在檔案系統 | Zeabur 設 N8N_DEFAULT_BINARY_DATA_MODE=filesystem 時,檔案在舊容器磁碟,不在資料庫備份 |
有需要保留的檔案就要另外搬。筆者的備份裡沒有 binary 資料;這不代表其他環境也沒有 |
| Python Code node | Zeabur 模板另有 task runner;本文單一 n8n 的配置沒有 Python runner | 搬之前先查 workflow 是否使用 Python。筆者沒有使用,所以這次不需要加 |
有 worker 且使用 queue 模式時,要把 binary 資料改存資料庫。 main 與 worker 不共用磁碟,筆者的 GKE 環境就把 N8N_DEFAULT_BINARY_DATA_MODE 設成 database。改設定不會自動搬走舊磁碟上的檔案,既有檔案仍要另外處理。
搬家時不要把秘密印到終端機、貼給 AI 或 commit。 包括備份檔內容、ENV 的值,以及雲端服務帳號的金鑰。筆者曾把一把雲端金鑰印到畫面上,當天就作廢換新。
筆者的環境:GKE+Cloud SQL,編輯器用 IAP 保護
筆者沿用公司既有的 Google Cloud 環境,所以選擇 GKE。公司原本就有 Kubernetes 叢集在執行其他內部資料服務,也已有 Terraform 管理設定;這不是單機搬遷必備的架構。
| 層 | 筆者的做法 | 對應 Docker Compose 的哪一個 |
|---|---|---|
| 資料庫 | Cloud SQL for PostgreSQL 17,只開私有 IP,每天自動備份 | postgres 服務 |
| n8n | GKE 上一個專用節點,跑 n8n main+worker+Redis(queue 模式,由 worker 接手執行工作) | n8n 服務,拆成多個元件 |
| 連資料庫 | Cloud SQL Auth Proxy,用 Workload Identity 登入,沒有金鑰檔 | Compose 內部網路 |
| 編輯器入口 | HTTPS Load Balancer+IAP:先通過公司 Google 帳號驗證,才看得到 n8n 登入頁 | 只綁 127.0.0.1 |
| webhook 入口 | /webhook、/form 路徑分到另一個不經 IAP 的後端,讓外部服務能呼叫 |
另外只開 webhook 路徑 |
IAP(Identity-Aware Proxy)是放在 n8n 登入頁前面的身分檢查。 使用者要先通過 Google 帳號驗證,才能進入 n8n 自己的登入流程。
編輯器與 webhook 要分開考慮。 整個網址都套 IAP,外部服務的 webhook 呼叫也會被擋住。筆者依路徑拆開:編輯器走 IAP,webhook 不走。公開的 webhook 仍要依工作流程需求設定驗證。
這套架構適合原本就有 GKE 的團隊。若只是要把 n8n 從 Zeabur 搬到單機環境,前面的 Docker Compose 配置就夠了。
附錄:Password Reset Script
找不回原密碼時,用這支腳本在新環境的資料庫重設。 密碼只在終端機輸入、畫面不顯示;腳本先在本機用 bcrypt(一種密碼雜湊方式,n8n 也用它)雜湊再寫進資料庫,明碼不會留在指令紀錄裡。存成 reset-n8n-password.sh,在 docker-compose.yml 所在目錄執行。
#!/usr/bin/env bash
# 重設 n8n 使用者密碼(Docker Compose 版)
# 用法:./reset-n8n-password.sh user@example.com
# 需要 htpasswd:macOS 內建;Ubuntu 安裝 apache2-utils
set -euo pipefail
EMAIL="${1:?請給要重設的 Email}"
read -rsp "新密碼(至少 8 碼,含 1 個大寫字母與 1 個數字):" PW; echo
read -rsp "再輸入一次:" PW2; echo
[ "$PW" = "$PW2" ] || { echo "兩次不一致"; exit 1; }
[[ ${#PW} -ge 8 && "$PW" =~ [A-Z] && "$PW" =~ [0-9] ]] || { echo "不符合 n8n 密碼規則"; exit 1; }
# 用 htpasswd 產生 bcrypt;n8n(bcryptjs)用 $2a$ 前綴
HASH="$(printf '%s' "$PW" | htpasswd -niBC 10 x | cut -d: -f2 | sed 's/^\$2y\$/$2a$/')"
unset PW PW2
docker compose exec -T postgres \
psql -U n8n -d n8n -v ON_ERROR_STOP=1 -v email="$EMAIL" -v hash="$HASH" <<'SQL'
UPDATE "user" SET password = :'hash' WHERE email = :'email' RETURNING email, "roleSlug";
SQL
echo "完成,到 http://localhost:5678 用 $EMAIL 登入。"
用法與判讀:
- 印出
UPDATE 1和那個 Email:成功,直接登入。 - 印出
UPDATE 0:資料庫裡沒有這個 Email,先用「踩過的坑」裡的 SQL 列出使用者。 - 腳本改的是新環境的資料庫,舊環境的密碼不受影響。正式切換時用最新備份重新還原,密碼也會回到舊的。
- 筆者的 GKE 版只差在連資料庫那一段:開一個暫時的 Pod(psql+Cloud SQL Auth Proxy),把同一句 UPDATE 送進去,跑完就刪掉 Pod。
