資料治理學院 Data Governance Academy
資料架構

n8n 搬家實戰:從 Zeabur 匯出資料庫,搬到自己的環境

文|廖力寬 Damon Liao · 資料治理學院 創辦人・CDMP Master

搬 n8n 最快的方法是整包搬資料庫:workflow(工作流程)、credential(連線憑證)、帳號與執行紀錄一口氣搬完,不用一個一個匯出匯入,完全無痛。關鍵是保留原本的 ENV(環境變數,尤其是 Encryption Key),在新環境準備好 PostgreSQL 與同版本的 n8n,先還原資料庫,再啟動 n8n。最後搭配一支 Password Reset Script 解決登入,原始碼附在文末。

這篇是筆者把一個 28 個 workflow 的 n8n 從 Zeabur 搬出來的實際紀錄。以下用 Docker Compose 示範操作,再介紹筆者公司的 GKE 環境。存在資料庫以外的檔案(例如存在磁碟上的圖片、文件)要另外搬,資料庫備份帶不走。

Zeabur(舊家) 你的電腦 新環境(Docker Compose) PostgreSQL 服務: Backup → 下載備份檔 (解開後是 data.sql) n8n 服務:複製 ENV (N8N_ENCRYPTION_KEY 最重要) 解開備份 → 只切出 n8n 的資料庫 (n8n.sql) 1 啟動 PostgreSQL 2 還原 n8n.sql、 核對筆數 3 帶同一把 Encryption Key 啟動 n8n 筆者:GKE+Cloud SQL, 編輯器用 IAP 保護
n8n 搬家流程:舊家 → 你的電腦 → 新環境。只要備份檔和 Encryption Key,workflow 與 credential 一次搬完。

整個搬家只有兩樣東西要從舊家帶走:資料庫備份與 Encryption Key;新環境照 1、2、3 的順序做,先還原再啟動 n8n。

關鍵一:在 Zeabur 後台手動觸發 PostgreSQL 備份

備份要進 PostgreSQL 服務操作,不是 n8n 服務。Zeabur 支援資料庫線上備份,備份時不用停止 PostgreSQL。

  1. 進 Zeabur 專案,點 PostgreSQL 那個服務。
  2. 切到 Backup 分頁,按 Backup。
  3. 等幾分鐘,確認列表出現新的備份。

試搬可以不停舊 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,先確認資料庫狀態。

  1. 只啟動 PostgreSQL。

    docker compose up -d postgres
  2. 只取出 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
  3. 還原到新資料庫,遇到錯誤就停止。 新資料庫叫 n8n,可以和來源的 zeabur 不同。有錯誤時先處理原因,不要接著啟動 n8n。

    docker compose exec -T postgres psql -U n8n -d n8n -v ON_ERROR_STOP=1 < n8n.sql
  4. 核對 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;'
  5. 先處理排程,再啟動 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。

為團隊規劃 CDMP 認證培訓

結合線上影片、每月一場的線上考題複習,以及進度追蹤與團體報名協助,依參訓人數與備考時程討論適合的安排。