Featured image of post 建置基於 Zipline 的私有化圖床與寫作工作流自動化指南

建置基於 Zipline 的私有化圖床與寫作工作流自動化指南

本指南詳細介紹如何在 Ubuntu 伺服器利用 Docker 部署 Zipline,結合 NPM 反向代理與 Cloudflare 邊緣快取,並整合 VS Code (PicList)、ShareX 及行動端自動化,打造具備自動刪除與隱私控制的私有化圖床工作流。

簡介

在撰寫技術文章或開發紀錄時,圖片的管理與嵌入往往會中斷寫作心流。本指南旨在建立一套完全私有化、高效且自動化的圖床工作流:在 Ubuntu 伺服器的 Docker 環境中部署 Zipline,搭配 Nginx Proxy Manager 做反向代理,再透過 Cloudflare 提供邊緣快取。

完成後,只需在 VS Code 中貼上圖片,PicList 便會自動完成上傳、分類、設定過期時間,並回傳支援跨域直連的 HTTPS 網址,全程無需離開編輯器。

本架構特別適合需要精確控制檔案生命週期(例如 30 天後自動刪除)、且重視資料隱私的開發者或內容創作者。

CDN 圖床示意

先決條件

開始本指南之前,請確認已具備下列環境:

  • 一台執行 Ubuntu 的伺服器,並已安裝 Docker 與 Portainer。
  • 一個已正常運行於 proxy_network(內網網段 192.168.32.0/20)的 Nginx Proxy Manager(NPM)容器。
  • 一個由 Cloudflare 代管的專屬網域(本指南以 your_domain 作為佔位符)。
  • 客戶端已安裝 VS Code(含 PicList 擴充套件或桌面版主程式)以及 ShareX。

全量參數提取表

以下為本架構中最終確認有效的所有核心參數設定值,供快速查閱與除錯對照:

組件參數名稱設定值說明
Docker 網路子網段192.168.32.0/20NPM 與 Zipline 所在的內部網路
UFW 防火牆允許規則allow from 192.168.32.0/20 to any port 3006若需透過主機 IP 存取時使用
Zipline 核心CORE_SECRET需大於 32 字元的隨機英數系統安全金鑰
Zipline 核心CORE_RETURN_HTTPStrue強制 API 回傳 HTTPS 網址
NPM 代理Forward Port3000走 Docker 內網直連容器預設埠口
NPM 進階client_max_body_size512M解除 Nginx 預設 1MB 上傳限制
PicList/ShareXAPI 地址https://your_domain/api/upload標準上傳端點
PicList/ShareXPOST 參數名file接收檔案的變數名稱
PicList/ShareXJSON 路徑files[0].url解析回傳網址的精確路徑
API 標頭x-zipline-folder(依實際建立的資料夾 ID)指定上傳目標資料夾
API 標頭x-zipline-deletes-at30d90d設定伺服器端自動刪除時間
Cloudflare快取規則 URI Path包含 /u/針對圖片目錄強制邊緣快取

圖床示意

逐步教學

步驟一:透過 Portainer 部署伺服器端環境

我們將使用 Portainer 的 Stack 功能部署 Zipline 與 PostgreSQL 資料庫。為避免容器名稱冗長,Compose 檔中已強制指定 container_name

  1. 登入 Portainer,進入 Stacks,點選 Add stack
  2. 命名為 zipline,並在 Web editor 中貼上以下 docker-compose.yml 內容:
services:
  zipline_db:
    container_name: zipline_db
    image: postgres:16
    restart: unless-stopped
    networks:
      - proxy_network
    environment:
      POSTGRES_USER: ${POSTGRESQL_USER:-zipline}
      POSTGRES_PASSWORD: ${POSTGRESQL_PASSWORD}
      POSTGRES_DB: ${POSTGRESQL_DB:-zipline}
      TZ: ${TZ:-Asia/Taipei}
    volumes:
      - ${ZIPLINE_DB_DIR:-zipline_pgdata}:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  zipline:
    container_name: zipline
    image: ghcr.io/diced/zipline
    restart: unless-stopped
    networks:
      - proxy_network
    ports:
      - '${ZIPLINE_PORT:-3000}:3000'
    environment:
      CORE_SECRET: ${CORE_SECRET}
      DATABASE_URL: postgres://${POSTGRESQL_USER:-zipline}:${POSTGRESQL_PASSWORD}@zipline_db:5432/${POSTGRESQL_DB:-zipline}
      TZ: ${TZ:-Asia/Taipei}
      CORE_RETURN_HTTPS: "true"
    depends_on:
      zipline_db:
        condition: service_healthy
    volumes:
      - '${ZIPLINE_UPLOADS_DIR:-./uploads}:/zipline/uploads'
      - '${ZIPLINE_PUBLIC_DIR:-./public}:/zipline/public'
      - '${ZIPLINE_THEMES_DIR:-./themes}:/zipline/themes'
    healthcheck:
      test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3000/api/healthcheck']
      interval: 15s
      timeout: 2s
      retries: 2

networks:
  proxy_network:
    external: true

volumes:
  zipline_pgdata:
  1. 往下捲動至 Environment variables(進階模式),填入以下環境變數(請務必替換密碼與金鑰):
TZ=Asia/Taipei
ZIPLINE_PORT=3006
CORE_SECRET=請替換為至少32字元的極度複雜隨機英數密碼
ZIPLINE_UPLOADS_DIR=/您的自訂路徑/zipline/uploads
ZIPLINE_PUBLIC_DIR=/您的自訂路徑/zipline/public
ZIPLINE_THEMES_DIR=/您的自訂路徑/zipline/themes
ZIPLINE_DB_DIR=zipline_pgdata
POSTGRESQL_USER=zipline_admin
POSTGRESQL_PASSWORD=請替換為高強度資料庫密碼
POSTGRESQL_DB=zipline_db
  1. 點選 Deploy the stack 完成部署。

Portainer Stack 部署成功後,顯示 zipline 與 zipline_db 容器皆處於 running 狀態的清單截圖

步驟二:Ubuntu 防火牆設定(UFW)

為了讓同屬 proxy_network 的容器(或透過主機轉發的流量)能正確存取 Zipline,需開放主機的 3006 埠口給特定 Docker 網段。透過 SSH 登入 Ubuntu 後執行:

sudo ufw allow from 192.168.32.0/20 to any port 3006
sudo ufw reload
sudo ufw status

步驟三:設定 Nginx Proxy Manager 反向代理

  1. 進入 NPM 後台,新增 Proxy Host。
  2. Details 分頁
  • Domain Names: your_domain
  • Scheme: http
  • Forward Hostname / IP: zipline(與 NPM 同屬 proxy_network,可直接填容器名稱)
  • Forward Port: 3000(容器內部埠口)
  1. Advanced 分頁(解決大檔案上傳限制):填入以下設定:
client_max_body_size 512M;
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
  1. SSL 分頁:申請或套用憑證,勾選 Force SSL 後儲存。

步驟四:Zipline 基礎設定與取得 Token

  1. 前往 https://your_domain 登入 Zipline。
  2. 建立管理員帳號後,進入 Settings,關閉 Enable Embeds(或 Format URL with Viewer)功能,確保圖片網址為純直連,不會跳轉至預覽頁面。
  3. 進入 Manage Account,在 Tokens 區塊點選 Create Token,複製並妥善保存。
  4. (選用)在首頁建立資料夾,並從網址列記錄該資料夾的 ID,後續設定上傳目標時會用到。

步驟五:設定客戶端 PicList(供 VS Code 使用)

  1. 開啟 PicList 主程式,進入「圖床設定」→「自定義 Web 圖床」。
  2. 新增設定,依序填入:
  • API 地址: https://your_domain/api/upload
  • POST 參數名: file
  • JSON 路徑: files[0].url
  • 自定義 Header:必須為合法 JSON 格式,可在此指定目標資料夾與過期時間(下方範例設為 30 天後刪除)。
{
  "authorization": "您在步驟四取得的 Token",
  "x-zipline-folder": "您的資料夾 ID",
  "x-zipline-deletes-at": "30d"
}
  • 自定義 Body:留空。
  1. 儲存並設為預設圖床。完成後,在 VS Code 中直接貼上圖片,即可自動取得 Markdown 格式的圖片連結。

自定義圖床設定

步驟六:設定客戶端 ShareX(供桌面截圖使用)

若要讓桌面截圖也能自動上傳至指定分類,請複製以下 JSON,並在 ShareX 中透過「目標 → 自訂上傳工具設定 → 匯入 → 從剪貼簿」匯入。匯入後,將 authorization 欄位替換為您的實際 Token。

{
  "Version": "17.0.0",
  "Name": "Zipline - ShareX 專用",
  "DestinationType": "ImageUploader, TextUploader, FileUploader",
  "RequestMethod": "POST",
  "RequestURL": "https://your_domain/api/upload",
  "Headers": {
    "authorization": "YOUR_API_TOKEN",
    "x-zipline-folder": "YOUR_FOLDER_ID",
    "x-zipline-deletes-at": "90d"
  },
  "URL": "{json:files[0].url}",
  "Body": "MultipartFormData",
  "FileFormName": "file"
}

sharex in Windows 設定


進階優化:Cloudflare 強制邊緣快取

為避免高流量文章消耗家用伺服器的出口頻寬,可透過 Cloudflare 快取圖片的讀取流量。

  1. 確認 Cloudflare DNS 中的網域已處於「橘雲(Proxied)」狀態。
  2. 進入 快取(Caching)→ Cache Rules,建立新規則。
  3. 觸發條件URI Path starts with /u/
  4. 快取行為
    • 選擇 Eligible for cache
    • Edge TTL:選擇 Override origin,設為 1 個月(建議不超過伺服器端的自動刪除時間)。
    • Browser TTL:選擇 Override origin,設為 1 天。
  5. 部署後,用瀏覽器無痕模式開啟任一圖片並按 F12,檢查 Response Headers 中的 cf-cache-status,顯示 HIT 即表示設定成功。

邊緣 TTL 示意圖說明


常見問題與故障排除

1. Portainer 中 Zipline 容器顯示 Unhealthy(橘燈)

  • 現象:資料庫與應用程式的啟動日誌皆正常,但容器被標記為不健康。
  • 根本原因:健康檢查指令(wget)與容器內部網路解析存在時序問題,屬於假警報,完全不影響服務正常運作。
  • 解法:若要消除警告,可在 Compose 檔中移除 healthcheck 區塊,或將測試網址改為 127.0.0.1

2. 上傳時出現 413 Request Entity Too Large

  • 現象:上傳稍大的檔案時直接報錯失敗。
  • 根本原因:Nginx 預設將請求 Body 限制在 1MB。
  • 解法:在 NPM 的 Advanced 設定中加入 client_max_body_size 512M; 解除限制。

3. PicList 日誌顯示 SUCCESS,但介面仍提示上傳失敗

  • 現象:後台日誌出現 200 OK,介面卻找不到回傳的圖片網址。
  • 根本原因:Zipline 回傳的 JSON 結構與 PicList 預期的路徑不符。曾嘗試修改「自定義 Body」格式,反而破壞了上傳的資料結構。
  • 解法:「自定義 Body」欄位必須完全留空,並將「JSON 路徑」精準設定為 files[0].url

4. 取得的直連網址自動跳轉至 /view/ 頁面

  • 現象:在瀏覽器中開啟 /u/ 開頭的網址,會自動跳轉為帶有黑色背景介面的 /view/ 頁面。
  • 根本原因:這是 Zipline 針對瀏覽器 HTML 請求的預覽保護機制。透過 Markdown ![]() 嵌入時不會觸發跳轉,正常使用不受影響。
  • 解法:若需徹底停用跳轉,進入 Zipline 後台 Settings,關閉 Enable Embeds 選項。

5. API 回傳的網址為 http 而非 https

  • 現象:上傳後取得的網址顯示為 http://your_domain/...
  • 根本原因:NPM 以 HTTP 轉發流量至 Docker 內網,導致 Zipline 誤判連線協議為 HTTP。
  • 解法:在 Portainer 的環境變數中加入 CORE_RETURN_HTTPS=true 強制覆蓋即可。

Android 設定

Android 端雖有多款第三方 Zipline 客戶端,但考量到最新 Android 版本(如 Pixel 9)的相容性與功能完整度,建議採用以下兩種方案。

方案一:原生 App(由 cssnr 開發)

這是目前社群中維護最積極、對 Zipline v4 API 支援最穩定的版本,能完整支援 Android「分享選單」,可直接從相簿將照片上傳至伺服器。

  1. Server URL:填入 https://your_domain(結尾不要加斜線 /)。
  2. Token:填入步驟四取得的 API Token。
  3. Name Format必填。若留空,部分版本會觸發邏輯錯誤,誤報「容量超限」或上傳卡死。建議填入 {filename}{random:8}
  4. 測試分享:在手機相簿選取照片,點選「分享」並選擇 Zipline App,確認進度條正常跑完。

技術筆記:若使用其他 App(如 Stef-00012 版本)遇到「1.89MB 超過 500MB」的矛盾報錯,通常是 Name Format 欄位驗證失敗所致,並非真正的容量限制,手動填入檔名格式即可解決。

方案二:PWA 輕量化方案(無需安裝)

若不想安裝額外的 APK,Zipline 內建的 Web UI 已針對行動裝置優化,體驗近似原生 App。

  1. 使用 VivaldiFirefox 瀏覽器開啟您的 Zipline 網址。
  2. 點選瀏覽器選單中的「新增至主螢幕」。
  3. 桌面圖示建立後,即可完整支援您在伺服器端設定的所有「上傳範本」與「自動刪除」規則,且不存在第三方 App 的解析相容性問題。

iOS 設定

透過 iOS 捷徑(Shortcuts)功能,可將手機照片快速上傳至 Zipline 伺服器並自動取得分享連結。

1. 建立捷徑與設定輸入

  • 新增一個捷徑。
  • 在捷徑設定中開啟「在分享工作表中顯示」。
  • 設定捷徑接收「影像」與「檔案」類型的輸入。

2. 設定網路請求(取得 URL 的內容)

這是捷徑的核心動作,依下列設定配置:

  • URLhttps://your_domain/api/upload(必須包含 /api/upload 路徑,否則傳輸會失敗)。
  • 方法:POST。
  • 標頭(Headers)
    • authorization:建議使用捷徑內建的變數存放 Token,避免明文暴露。
    • x-zipline-folder:填入目標資料夾 ID(選填)。
    • x-zipline-deletes-at90d,設定 90 天後自動刪除(不需自動刪除可省略)。
    • x-zipline-image-compression-percent85,壓縮品質 85%(不需壓縮可省略)。
    • x-zipline-image-compression-typewebp,強制轉換為 WebP 格式(選填)。
  • 要求主體:選擇「表單」。
  • 表單欄位:新增一個欄位,鍵(Key)填入 file,值(Value)選擇「捷徑輸入」的檔案。

3. 解析回傳資料並複製連結

上傳成功後,Zipline 會回傳 JSON 格式的結果,依以下步驟解析取得網址:

  • 取得字典值:從「URL 的內容」中取出 files 的值。
  • 從列表取得項目:取上述結果的「第一個項目」。
  • 取得字典值:從該項目中取出 url 的值。
  • 拷貝至剪貼簿:將 url 複製到剪貼簿,方便後續貼上使用。

ios 捷徑流程圖

4. 常見問題排除

  • 錯誤訊息:無法從「RTF」轉換到「辭典」: 通常代表伺服器回傳的是 HTML 錯誤頁面,而非預期的 JSON。請確認 API URL 是否正確(例如有無遺漏 /api/upload),以及 Authorization Token 是否仍有效。

  • 關於標籤(Tags)設定: 目前 Zipline 的上傳 API(/api/upload)不支援在標頭中直接指定標籤。若有分類需求,請改用 x-zipline-folder 標頭指定資料夾來管理。

結論

透過本文的架構,我們將底層伺服器、反向代理、前端寫作工具與邊緣快取網路整合為一套完整的工作流。它不僅消除了 Markdown 寫作時圖片散落各處的痛點,「過期自動刪除」與「資料夾分類指派」功能更能有效管理伺服器硬碟空間,降低長期維運成本。無論是從桌面、手機或 VS Code,都能以一致的體驗完成圖片上傳,是一套穩固且具備高度擴充性的個人化數位資產發布流程。