Featured image of post 如何將 Docker PostgreSQL 資料庫從具名磁碟卷安全轉移至絕對路徑

如何將 Docker PostgreSQL 資料庫從具名磁碟卷安全轉移至絕對路徑

完整記錄將 Docker PostgreSQL 具名磁碟卷遷移至宿主機絕對路徑的 SOP,包含進入容器查詢真實角色與資料庫名稱的關鍵除錯細節。

1. 簡介

在 Docker 容器化部署中,預設的具名磁碟卷 (Named Volume) 雖然方便,但其資料隱藏在 Docker 的內部系統目錄中,不利於直接備份與後續維護。將資料庫轉移至宿主機的絕對路徑 (Bind Mount),能顯著提升資料的掌控度與備份便利性。

本指南以 Zipline 服務搭配 PostgreSQL 16 為例,完整記錄從舊有具名磁碟卷搬移至自訂絕對路徑的標準作業流程 (SOP)。本次指南特別強化了「如何進入容器內部查詢資料庫與使用者名稱」的關鍵細節。此流程不僅適用於 Zipline,其底層邏輯與除錯方法亦可完全套用於後續的 Immich 或其他依賴 PostgreSQL 的服務移轉。

2. 先決條件

在開始執行轉移之前,請確保您的環境具備以下條件:

  • 具備伺服器的 SSH 終端機存取權限 (具有 sudo 權限)。
  • 已安裝 Docker 與 Portainer。
  • 來源服務 (如 Zipline) 處於可正常停止的狀態。
  • 熟悉基本的 Docker Compose 語法。

伺服器結構示意圖

資料庫示意圖

3. 變數與環境參數清單

為保護隱私與提升部署靈活性,本指南的所有設定皆採用環境變數處理。以下為本次轉移過程中所提取並確認有效之參數清單:

參數類別變數名稱 / 實際設定檔配置說明
路徑設定${DOCKER_DATA_PATH}宿主機存放 Docker 資料的根目錄 (例如:/mnt/cloud1/dockerData)。
舊有磁碟卷zipline_zipline_pgdataDocker 自動管理的舊有 PostgreSQL 具名磁碟卷名稱。
資料庫憑證${POSTGRESQL_USER}實際寫入舊資料庫中的超級管理員角色名稱 (取代預設的 postgres)。
資料庫憑證${POSTGRESQL_DB}實際寫入舊資料庫中的目標資料庫名稱。
資料庫憑證${POSTGRESQL_PASSWORD}資料庫連線密碼。
內部通訊埠3000Zipline 應用程式容器對 Traefik 暴露的內部通訊埠。

4. 逐步教學

4.1. 步驟 1:確認舊有磁碟卷名稱並停止服務

在搬移資料前,必須先停止容器,確保資料庫檔案不在寫入狀態。

  1. 透過 SSH 登入伺服器,找出 PostgreSQL 正在使用的具名磁碟卷名稱:
docker volume ls | grep pgdata
  1. 進入 Portainer 的 Stacks 介面,將目標服務 (Zipline) 停止 (Stop this stack)。

4.2. 步驟 2:建立目標絕對路徑與設定權限

PostgreSQL 在容器內預設使用 UID 999 執行。為了讓容器啟動後有權限讀寫宿主機上的絕對路徑,必須正確設定資料夾擁有者。

# 1. 建立存放資料庫檔案的絕對路徑 (請替換變數為實際路徑)
sudo mkdir -p ${DOCKER_DATA_PATH}/zipline/pgdata

# 2. 將該目錄的擁有者更改為 999 (PostgreSQL 專用 UID)
sudo chown -R 999:999 ${DOCKER_DATA_PATH}/zipline/pgdata

4.3. 步驟 3:使用救難容器進行資料搬移

為確保檔案權限與隱藏屬性完整轉移,請使用輕量級的 Alpine 容器作為搬遷橋樑。

# 使用救難容器將舊卷資料完整複製到新目錄
# 參數 -a 代表封存模式 (保留權限),-v 代表顯示過程
docker run --rm \
  -v zipline_zipline_pgdata:/from \
  -v ${DOCKER_DATA_PATH}/zipline/pgdata:/to \
  alpine ash -c "cp -av /from/. /to/"

4.4. 步驟 4:修改 Docker Compose 設定檔並啟動

回到 Portainer 的 Stack 編輯器,將資料庫服務的 Volume 掛載方式改為絕對路徑。

version: '3.8'

services:
  zipline_db:
    image: postgres:16
    container_name: zipline_db
    restart: unless-stopped
    networks:
      - default
    environment:
      POSTGRES_USER: ${POSTGRESQL_USER}
      POSTGRES_PASSWORD: ${POSTGRESQL_PASSWORD}
      POSTGRES_DB: ${POSTGRESQL_DB}
      TZ: Asia/Taipei
    volumes:
      # 將原本的具名磁碟卷替換為宿主機的絕對路徑
      - ${DOCKER_DATA_PATH}/zipline/pgdata:/var/lib/postgresql/data
      
# ... (省略其他服務設定) ...

# 務必刪除或註解掉檔案最底部的 volumes 宣告區塊
# volumes:
#   zipline_pgdata:

更新 Stack 並啟動容器 (Update the stack -> Re-create containers)。此時資料庫容器會啟動,但由於 PostgreSQL 偵測到已有資料,會跳過初始化。這表示它不會理會您剛剛在環境變數中設定的新名稱,而是沿用舊有設定。

4.5. 步驟 5:查詢舊資料庫內部的角色與名稱 (關鍵細節)

這是在搬移過程中最重要的除錯步驟。如果應用程式 (如 Zipline) 報錯無法連線,通常是因為舊資料庫內部的名稱與現有 .env 不符。我們必須進入資料庫內部進行查詢。

  1. 嘗試一般登入
# 嘗試使用您的環境變數帳號登入
docker exec -it zipline_db psql -U ${POSTGRESQL_USER} -d postgres
  1. (若失敗) 使用系統管理員強制登入: 如果在建立舊資料庫時,預設的 postgres 帳號已被取代,上述指令或使用 -U postgres 可能會報錯 FATAL: role "postgres" does not exist。此時請改用作業系統的 postgres 權限強制進入:
# 透過容器內的 linux 使用者 postgres 身份進入 psql,繞過角色驗證
docker exec -u postgres -it zipline_db psql
  1. 執行查詢指令: 成功進入 psql 提示字元 (如 postgres=#zipline_admin=#) 後,請輸入以下指令進行盤點:
  • 查詢所有使用者 (Roles):輸入 \du 並按下 Enter。查看清單中擁有 Superuser 權限的角色名稱究竟是什麼。

  • 查詢所有資料庫 (Databases):輸入 \l 並按下 Enter。查看清單中除了 templatepostgres 之外,存放您資料的實體資料庫名稱是什麼。

  • 退出介面:輸入 \q 退出 psql。

Gemini_Generated_Image_y0e8agy0e8agy0e8.webp

4.6. 步驟 6:校正資料庫內部的名稱與密碼

根據步驟 5 查詢到的結果,若發現舊名稱與您現在 .env 中設定的 ${POSTGRESQL_USER}${POSTGRESQL_DB} 不同,請在 psql 介面中執行以下指令強制統一 (請替換為實際的舊名稱與變數):

-- 1. 將舊角色名稱改為新的環境變數名稱
ALTER ROLE 舊角色名稱 RENAME TO 您的新使用者名稱;

-- 2. 強制更新密碼以確保連線無誤
ALTER ROLE 您的新使用者名稱 WITH PASSWORD '您的新密碼';

-- 3. 將舊資料庫名稱改為新的環境變數名稱
ALTER DATABASE 舊資料庫名稱 RENAME TO 您的新資料庫名稱;

完成校正後,重新啟動應用程式容器 (如 Zipline),即可恢復正常連線。

5. 常見問題與故障排除

5.1. 故障 1:Traefik Dashboard 找不到路由,網頁顯示 404

  • 現象:容器皆顯示 Running,但 Traefik 儀表板未顯示該服務網址。
  • 無效嘗試:反覆檢查 Traefik 的 Labels 語法與 Cloudflare DNS 設定。
  • 解決邏輯:經查核為 Portainer 中應用程式容器 (zipline) 狀態顯示為 unhealthy。Traefik 預設會隱藏不健康的容器路由。原因在於 Docker Compose 中的 healthcheck 指令依賴的工具 (如 wget) 在 Zipline 容器內不存在,或因資料庫連線延遲導致檢查超時。
  • 解決方法:先於 docker-compose.yml 中將應用程式服務的 healthcheck 區塊全數註解,強制容器狀態恢復為健康,使 Traefik 路由正常載入並方便進行後續連線除錯。

6. 進階優化

  • 健康檢查指令優化:若日後需恢復應用程式容器的健康檢查機制,為避免因啟動時間差或缺少工具造成的誤判,建議加入 start_period 參數,並使用相容性較高的 Shell 邏輯:
  healthcheck:
    # 改用 CMD-SHELL 並結合 curl 與 wget 提高相容性
    test: ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || wget -q --spider http://localhost:3000/api/healthcheck || exit 1"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 60s # 給予應用程式 60 秒的啟動緩衝期
  • 舊資料清理:當確認新絕對路徑的資料庫穩定運行數日後,可執行 docker volume rm zipline_zipline_pgdata 以釋放伺服器儲存空間。

7. 結論

將 PostgreSQL 資料庫從具名磁碟卷轉移至絕對路徑的核心挑戰,在於理解 Docker 容器啟動機制與資料庫內部既有資料的關聯。只要確保檔案權限 (UID 999) 正確,並學會進入容器內部使用 \du\l 指令查驗真實狀態,再配合 ALTER 指令校正,即可順利完成無痛轉移。這套流程與排錯邏輯,亦可完全套用於後續 Immich 等任何依賴 PostgreSQL 服務的資料庫搬遷。