SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

自架圖表工具比較:draw.io、Excalidraw、Kroki

比較 draw.io、Excalidraw 與 Kroki 自架於 VPS 的差異:前兩者在瀏覽器繪圖,Kroki 會將圖表文字送至伺服器,這決定自架是否真正提升隱私。

應執行哪個自架圖表工具?

自架圖表工具可分為兩種類型,而類型比功能清單更重要。draw.io 和 Excalidraw 是瀏覽器應用程式:容器提供 JavaScript,由瀏覽器負責繪圖,伺服器不會看見圖表內容。Kroki 則相反。您透過 HTTP 傳送圖表文字,Kroki 再回傳影像,因此每張圖表都會經過您自己的機器。

如果您要在 wiki 旁使用完整編輯器,請執行 draw.io。如果您要快速繪製草圖,且接受內容只儲存在繪圖所用的瀏覽器中,請執行 Excalidraw。如果您的圖表是與所描述程式碼一同儲存在 git 中的文字,請執行 Kroki。

自行託管圖表工具實際改變的內容

請準確確認哪些部分會接觸您的伺服器,因為這項事實會決定自行託管帶來的是隱私,還是只有可用性。

  • draw.io 在瀏覽器中進行渲染。您的容器提供應用程式程式碼。檔案會儲存到您指定的位置。
  • Excalidraw 在瀏覽器中進行渲染,並將目前場景保存在該瀏覽器的本機儲存空間中。伺服器端不會寫入任何資料。
  • Kroki 在伺服器上進行渲染。圖表原始碼與完成的圖片都會存在您的容器內。

只有第三種情況會將資料移至您控制的硬體。前兩種情況中,自行託管帶來的是資產控制權與可用性:JavaScript 由您的主機提供,因此即使第三方服務中斷、變更條款,或您的網路無法連線到該服務,編輯器仍可繼續運作。對部分團隊而言,這確實具有實際價值。但這與「圖表永遠不會離開辦公場所」是不同的說法。

draw.io:不儲存任何資料的官方容器

此專案會發布自己的 image,README 中的快速啟動方式只有一行。

docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawio

這會在該主機的所有位址上發布編輯器。在 VPS 上,請將發布的埠繫結至 loopback,再透過反向代理或 SSH tunnel 存取。

docker run -d --name drawio --restart unless-stopped -p 127.0.0.1:8080:8080 jgraph/drawio

透過 tunnel 開啟 http://127.0.0.1:8080/?offline=1&https=0。README 將 ?offline=1 稱為「停用 cloud storage 支援的安全性功能」。未設定此選項時,編輯器會提供 Google Drive、OneDrive 與 GitHub 作為儲存目標,而這些都是他人的伺服器。

繫結至 127.0.0.1 才能讓該埠不暴露於公開網際網路。單純使用 -p 8080:8080 不會受到 ufw 過濾,因為 Docker 會在 ufw 管理的 chain 之前插入自己的 iptables 規則。因此,防火牆設定看似正確,但該埠仍會回應所有來源。Docker 直接繞過 ufw 發布埠說明其運作機制與修正方式。

只要編輯器不在 localhost 上,就有兩個重要的環境變數。

services:
  drawio:
    image: jgraph/drawio
    container_name: drawio
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      DRAWIO_SERVER_URL: "https://drawio.example.com/"
      DRAWIO_BASE_URL: "https://drawio.example.com"

結尾的斜線不是筆誤。README 將 DRAWIO_SERVER_URL 定義為「帶結尾斜線的公開部署 URL」,並將 DRAWIO_BASE_URL 定義為「不帶結尾斜線的相同 URL」,供 viewer、lightbox 與 embed code 路徑使用。若編輯器是以 https://www.example.com/drawio/ 這類子路徑提供服務,兩個值都必須包含該子路徑,因為應用程式會依據這些值建立 viewer 與 embed URL。

持久性:完全不存在,這就是設計方式。 該 Compose file 中沒有 volume,因為容器不會保存任何圖表資料。.drawio file 是編輯器交給瀏覽器的 XML,實際儲存位置由你選擇的儲存目標決定:下載到自己的電腦,或交給嵌入編輯器的應用程式。請備份該儲存目的地。若目的地是 VPS 上的資料夾,真正需要保護的就是該資料夾,以及你用來存取它的 file manager,因為 draw.io 不會保留任何副本。

仍會離開伺服器的資料。 比以往少,因為 export 不再傳送到其他地方。README 現在將 EXPORT_URL 與 DRAWIO_SELF_CONTAINED 列為已移除且不具作用的變數。它們原本會將編輯器指向獨立的 jgraph/export-server image,而該 image「已結束生命週期」。現在 entrypoint 會將 window.EXPORT_URL = null 寫入 PreConfig.js,告知編輯器不存在 export service。實際上,Export as > PDF 會開啟瀏覽器的列印對話方塊,因此 PDF 不會提供包含圖表副本的選項,也會隱藏由伺服器產生的 PNG 與 JPEG export。在瀏覽器中產生的 PNG、JPEG 與 SVG export 仍可使用。ENABLE_DRAWIO_PROXY 預設為關閉;啟用後會提供 /proxy endpoint,代表瀏覽器代為取得外部 image URL。除非確實需要,否則請維持關閉。

Excalidraw:沒有後端伺服器的靜態套件

官方映像頁面提供以下命令。

docker run --rm -dit --name excalidraw -p 5000:80 excalidraw/excalidraw:latest

基於與前述相同的原因,將發布的連接埠移至 loopback。

docker run -d --name excalidraw --restart unless-stopped -p 127.0.0.1:5000:80 excalidraw/excalidraw:latest

容器內的 nginx 會在連接埠 80 提供編譯後的 JavaScript 套件。發布的映像經壓縮後約為 41 MB(Docker Hub,August 2026),由此可知其中包含的內容很少。沒有資料庫、工作階段儲存區或上傳目錄,因為伺服器沒有需要儲存的內容。

映像頁面明確說明了限制:「目前,自行代管的執行個體不支援分享或協作功能。」介面中仍會顯示這些按鈕,因此了解原因很重要。即時協作需要 websocket 伺服器,該伺服器另行發布為 excalidraw/excalidraw-room。分享連結需要儲存服務,以保存加密後的場景。兩者的位址會在建置時以 Vite 變數(VITE_APP_WS_SERVER_URL、VITE_APP_BACKEND_V2_GET_URL、VITE_APP_BACKEND_V2_POST_URL)編譯到套件中,而 repository 內的 production 值指向 Excalidraw 自有的代管服務。Vite 會在建置期間代換這些值,因此它們最終會成為 JavaScript 內的字串常值。將這些值設為容器環境變數不會產生任何作用,因為沒有程式碼在執行階段讀取它們。若要將協作功能指向自己的 room server,必須使用自訂值從原始碼建置前端。規劃架構前,請先確認該伺服器的狀態:截至 August 2026,Docker Hub 上的 excalidraw/excalidraw-room 映像已超過兩年未重新建置。

繪圖實際儲存的位置。 場景位於瀏覽器的 local storage 中,儲存在該裝置、該 origin 的資料內。在 private window 中開啟相同 URL,畫布會是空的;這是最快的自行驗證方式。清除網站資料會刪除繪圖,而且沒有伺服器副本可供還原。因此,請教使用者使用「儲存至...」,並保留 .excalidraw 檔案。該檔案是 JSON,應放在會進行備份的位置。共用執行個體會為每位使用者提供自己的私人畫布。請將它視為託管在伺服器上的個人草稿本。

Kroki:以程式碼描述圖表,並在伺服器上轉譯

Kroki 是位於多個轉譯器前方的單一 HTTP gateway。您以 POST 傳送文字,並取得 SVG 或 PNG。Graphviz、PlantUML、D2 及其他數個轉譯器已內建於 gateway 映像檔。Mermaid、BPMN 與 Excalidraw 轉譯功能則位於 companion container,因此使用 Compose 執行最為合適。以下是 Kroki 文件中的範例。

services:
  kroki:
    image: yuzutech/kroki
    depends_on:
      - mermaid
      - bpmn
      - excalidraw
    environment:
      - KROKI_MERMAID_HOST=mermaid
      - KROKI_BPMN_HOST=bpmn
      - KROKI_EXCALIDRAW_HOST=excalidraw
    ports:
      - "8000:8000"
    tmpfs:
      - /tmp:exec
  mermaid:
    image: yuzutech/kroki-mermaid
    expose:
      - "8002"
  bpmn:
    image: yuzutech/kroki-bpmn
    expose:
      - "8003"
  excalidraw:
    image: yuzutech/kroki-excalidraw
    expose:
      - "8004"

expose 不會向 host 發布任何內容,因此 companion container 只能從 Compose network 由 gateway 存取。這正是所需的行為。除非呼叫它的 wiki 執行在其他 host,否則請將 gateway 那一行改為 "127.0.0.1:8000:8000"。如果您之前未曾在伺服器上撰寫 Compose 檔案,在 VPS 上執行 Docker Compose 說明檔案配置與 docker compose up -d 週期。

請依此順序執行兩項 smoke test,因為它們失敗的原因不同。

curl -s -X POST http://127.0.0.1:8000/graphviz/svg \
  -H 'Content-Type: text/plain' \
  --data-binary 'digraph G {Hello->World}' | head -c 60

Graphviz 在 gateway 內執行,因此這裡的 SVG 文件可證明 gateway 本身正常。接著測試會跨越 container 的路徑。

curl -s -X POST http://127.0.0.1:8000/mermaid/svg \
  -H 'Content-Type: text/plain' \
  --data-binary 'graph TD; A-->B;' | head -c 60

第二個指令產生 SVG,表示 KROKI_MERMAID_HOST 已解析且 companion 已回應。如果第一個測試成功、第二個失敗,問題位於兩個 container 之間,因此請先查看 docker compose logs kroki,不要先檢查圖表語法。

GET 形式會將圖表編碼到 URL 中,因此 wiki 不需任何 plugin 就能嵌入圖片。文件提供了這個 encoder。

cat hello.dot | python -c "import sys; import base64; import zlib; print(base64.urlsafe_b64encode(zlib.compress(sys.stdin.read().encode('utf-8'), 9)).decode('ascii'))"

在 Ubuntu 上,該指令會輸出 python: command not found,因為系統提供 python3,但沒有未指定版本的 python。請使用 python3。輸出會接在格式為 /{diagram-type}/{output-format}/{encoded-diagram} 的 URL 末端,任何 <img> tag 都能指向該 URL。這有長度上限:KROKI_MAX_URI_LENGTH 預設為 4096 bytes,因此較長的圖表必須透過 POST 傳送。

Kroki 會讀取您傳送的文字,因此真正重要的是它的安全性設定。 KROKI_SAFE_MODE 預設為 SECURE,這是 3 個層級中限制最嚴格的設定;KROKI_PLANTUML_ALLOW_INCLUDE 預設為 false。這些預設值的原因是,PlantUML 的 !include 指示詞會從轉譯器的角度讀取檔案與 URL。若在任何人都能存取的 endpoint 上放寬設定,就等於讓網際網路取得執行於 container 內的檔案讀取器。除非您知道需要哪個 include path,否則請維持這些設定;確有需要時,再以 KROKI_PLANTUML_INCLUDE_PATH 指定路徑。

記憶體:小型 VPS 上哪個服務最耗資源

了解各容器執行的內容後,就能預測其資源使用順序。

  • Excalidraw 映像檔使用 nginx 提供靜態檔案,三者相比,耗用資源低很多。
  • draw.io 執行 Tomcat,也就是 Java 應用程式伺服器,因此無論是否有人繪圖,都會載入 JVM(Java virtual machine)。
  • Kroki gateway 也是 Java 服務,手動安裝時以 jar 檔提供。
  • mermaid companion 最耗資源。其 Dockerfile 會安裝 Chromium,並設定 PUPPETEER_EXECUTABLE_PATH=/usr/lib/chromium/chrome,因為 Mermaid 會在實際的瀏覽器引擎中進行渲染。

因此,閒置時的數值參考價值很低。真正重要的是渲染圖表時的尖峰,而 KROKI_MERMAID_MAX_CONCURRENCY 預設為 6,因此最多可同時進行 6 次瀏覽器渲染。請在自己的主機上測量,不要直接採信已發布的數值。

docker stats --no-stream
docker system df

先在所有服務都閒置時執行一次,再於反覆渲染大型 mermaid 圖表時執行一次。如果小型方案上的尖峰值過高,請直接設定上限,不要自行猜測:設定 Compose 服務的記憶體限制會說明語法,以及容器達到上限時的行為。移除 mermaid companion 也是可行的做法,因為 gateway 仍會提供其中內建的所有 renderer。

這些服務都沒有提供使用者模型,因此要在前面加上代理層

draw.io 沒有帳號。Excalidraw 沒有帳號。Kroki 會處理收到的任何請求。登入功能必須由代理層提供。

sudo apt update && sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd alice

htpasswd -c 會建立檔案並覆寫現有檔案,因此第一次傳入 -c,之後不要再傳入。

server {
    listen 443 ssl;
    server_name drawio.example.com;

    location / {
        auth_basic "diagrams";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

使用 sudo nginx -t && sudo systemctl reload nginx 套用設定。nginx -t 部分很重要:設定檔有錯誤時重新載入會保留舊設定繼續執行,因此網站仍可運作,但變更不會生效。逐行說明反向代理設定涵蓋此片段省略的標頭區塊與憑證路徑。

Basic authentication 不適合 Kroki,原因值得了解。wiki 頁面會使用 <img> 標籤嵌入 Kroki 圖片。讀者的瀏覽器會以子資源方式擷取該 URL,但不會將你的認證資訊傳送到不同的 origin,因此請求會回傳 401,頁面上的每個圖表都會顯示為損壞的圖片。請改為讓 Kroki 不暴露在公開網際網路上。將它放在與 wiki container 相同的 Docker network,讓 wiki 透過服務名稱連線,完全不要將任何連接埠發佈到主機。Compose network 如何解析服務名稱說明了這項作法的運作方式。

與自架 Wiki 並存的圖表

這通常是人們想要這些工具的原因。Wiki 頁面需要圖片,而沒有人希望該圖片只是某台筆記型電腦上的螢幕擷取畫面。

BookStack 原生支援自架編輯器。其預設嵌入 URL 為 https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1,在 .env 中加入一行即可將其移至您的容器。

DRAWIO=https://drawio.example.com/?embed=1&proto=json&spin=1&configure=1

請完整複製查詢字串。BookStack 文件指出,embed=1&proto=json&spin=1「是讓 BookStack 整合正常運作所需的設定」,因為這些參數會選擇兩個頁面彼此通訊時使用的 JSON 訊息協定。同一頁也提到 stealth=1「如果您不希望使用其他外部服務」,這是在自架的目的包含停止對外呼叫時應加入的選項。完成設定後,BookStack 會將圖表儲存至自身的圖片儲存區,位置與頁面相鄰,因此您原本執行的 Wiki 備份也會同時備份圖表。

如果尚未決定使用哪個 Wiki,請先處理這項選擇。選擇 BookStack、Wiki.js 與 Outline 是先前的決策,因為 Wiki 會決定圖表如何附加至頁面,也會決定您要搭配哪些工具。

錯誤模式與您會看到的字串

繪圖編輯器在 BookStack 中開啟後持續轉圈。 轉圈圖示代表 spin=1 正在等待永遠不會到達的交握訊息。確認您的 DRAWIO 值中包含 embed=1&proto=json&spin=1,並確認主機部分沒有拼字錯誤。

HTTPS wiki 中的編輯器框架維持空白。 瀏覽器主控台回報混合內容,表示正在 https:// 中載入 http://。瀏覽器會封鎖此框架,因此 draw.io 永遠不會執行。請透過 HTTPS 提供編輯器。

Kroki 回傳 413 Request Entity Too Large。 該字串來自 nginx,不是 Kroki。nginx 的 client_max_body_size 預設值為 1 MB,大型 PlantUML 原始碼會超過此限制,因為 PlantUML 會在 gateway 內進行轉譯。Kroki 自身的 KROKI_MAX_BODY_SIZE 預設值也是 1mb,但只有 diagrams.net、Excalidraw 和 Mermaid companion 容器會使用它,因此大型 Mermaid 原始碼可能會超過其中任一限制。請先提高 client_max_body_size;如果圖表會經過 companion,也請提高該 companion 上的 KROKI_MAX_BODY_SIZE。

Mermaid 失敗,但 graphviz 可正常運作。 gateway 運作正常,但未連線至 companion。使用 docker compose ps 確認服務正在執行,再確認 KROKI_MERMAID_HOST 是否符合服務名稱,因為它的預設值是 127.0.0.1;在 gateway 容器內,這代表 gateway 本身。

Excalidraw 協作始終無法連線。 如果您針對自己的 room server 建置前端,並將其放在 nginx 後方,代理伺服器必須使用 proxy_set_header Upgrade $http_upgrade; 和 proxy_set_header Connection "upgrade"; 升級連線。缺少這兩項設定時,websocket 交握會被當作一般 HTTP 請求處理,工作階段因此無法開始。

清理瀏覽器後畫布變成空白。 場景原本儲存在該裝置的 local storage 中,伺服器上沒有副本。解決方法不是設定,而是養成習慣:任何值得保留的內容都要匯出 .excalidraw 檔案。

FAQ

自行代管 draw.io 能保護我的圖表隱私嗎?

它會將應用程式程式碼保留在您的伺服器上,但這與保護資料隱私是兩回事。draw.io 在瀏覽器中轉譯,因此容器根本不會持有圖表。隱私取決於您將檔案儲存在哪裡,以及您保留啟用哪些對外連線。使用 ?offline=1 停用雲端儲存目標,並保持 ENABLE_DRAWIO_PROXY 關閉。匯出也不會傳送任何資料:此映像未設定匯出服務,因此 PDF 會透過瀏覽器的列印對話方塊處理,而 DRAWIO_SELF_CONTAINED 不再有任何作用。

為什麼我自行代管的 Excalidraw 無法協作?

官方映像頁面指出,自行代管「不支援分享或協作功能」。即時協作需要獨立的 excalidraw/excalidraw-room websocket 伺服器,分享連結則需要儲存服務。兩者的位址會在建置時,以 VITE_APP_WS_SERVER_URL 等 Vite 變數編譯至 JavaScript bundle,因此在執行中的容器設定環境變數不會生效。若要使用自己的 room server,必須以自訂值從原始碼建置前端。

如何在自己的伺服器上轉譯 Mermaid 圖表?

執行 Kroki 及其 mermaid companion container,並將 KROKI_MERMAID_HOST 設為該服務名稱。接著將圖表文字 POST 至 /mermaid/svg,並從回應讀取 SVG;或者將圖表編碼至 GET URL,再讓 <img> 標籤指向該 URL。由於 Mermaid 需要瀏覽器引擎,companion 會透過 Puppeteer 驅動 Chromium,因此必須預留記憶體:KROKI_MERMAID_MAX_CONCURRENCY 預設同時轉譯 6 個圖表。

這些工具前面需要設定密碼嗎?

需要,因為這些工具都沒有帳戶功能。draw.io 和 Excalidraw 會將完整編輯器提供給任何找到 URL 的人,而 Kroki 會轉譯傳送給它的任何文字。對兩個編輯器而言,在反向代理上設定基本驗證就足夠。至於 Kroki,請將其保留在與 wiki 共用的 Docker network 中,不要公開發布,因為讀者瀏覽器發出的 <img> 請求不會將認證資訊傳送至另一個來源,所有內嵌圖表都會因此失效。

#diagrams#drawio#excalidraw#mermaid#kroki#docker