SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-21

draw.io、Excalidraw 与 Kroki 自托管对比

对比 draw.io、Excalidraw 和 Kroki 的浏览器端与服务器端渲染,确认哪些图表数据会经过 VPS,以及自托管何时真正带来隐私保护。

应运行哪种自托管图表工具?

自托管图表工具有两种形态,形态比功能列表更重要。draw.io 和 Excalidraw 是浏览器应用:容器提供 JavaScript,由浏览器完成绘图,服务器不会看到图表内容。Kroki 则相反。您通过 HTTP 向它发送图表文本,它会返回图像,因此每个图表都会经过您自己的计算机。

如果您希望在 wiki 旁边使用完整编辑器,请运行 draw.io。如果您需要快速草图工具,并且接受图表只保存在绘制它的浏览器中,请运行 Excalidraw。如果图表以文本形式存储在 git 中,并与所描述的代码放在一起,请运行 Kroki。

自托管图表工具实际改变了什么

请准确确认哪些部分会接触您的服务器,因为这一点决定了自托管带来的是隐私保护,还是仅仅提高可用性。

  • draw.io 在浏览器中渲染。您的容器提供应用代码。文件会保存到您指定的位置。
  • Excalidraw 在浏览器中渲染,并将当前场景保存在该浏览器的本地存储中。服务器端不会写入任何内容。
  • Kroki 在服务器上渲染。图表源文件和生成的图像都会存在于您的容器中。

只有第三种情况会将数据存储到您控制的硬件上。对于前两种情况,自托管带来的是资源控制权和可用性:JavaScript 从您的主机提供,因此即使第三方服务中断、修改服务条款,或从您的网络无法访问,编辑器仍可继续工作。对一些团队来说,这具有实际价值。但这与“图表永远不会离开办公场所”是不同的说法。

draw.io:不存储任何数据的官方容器

该项目发布了自己的镜像,其 README 中的快速开始命令只有一行。

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

这会让编辑器监听主机的所有地址。在 VPS 上,应将发布端口绑定到 loopback,再通过反向代理或 SSH 隧道访问。

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

通过隧道打开 http://127.0.0.1:8080/?offline=1&https=0。README 将 ?offline=1 称为“禁用云存储支持的安全功能”。如果不启用它,编辑器会提供 Google Drive、OneDrive 和 GitHub 作为保存目标,而这些都是其他组织的服务器。

绑定到 127.0.0.1 才能避免该端口暴露到公网。直接使用 -p 8080:8080 不会受到 ufw 过滤,因为 Docker 会在 ufw 管理的链之前插入自己的 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 和嵌入代码路径使用。如果通过子路径(例如 https://www.example.com/drawio/)提供编辑器,这两个值都必须包含该子路径,因为应用会根据它们构建 viewer 和嵌入 URL。

持久化:不存在,这就是设计。该 Compose 文件中没有 volume,因为容器不保存图表数据。.drawio 文件是编辑器交给浏览器的 XML,您选择的保存目标决定文件最终存放位置:可以是您本机上的下载目录,也可以是嵌入编辑器的应用。请备份该保存目标。如果保存目标是 VPS 上的目录,那么需要保护的就是该目录,以及您用来访问它的文件管理器,因为 draw.io 不会保留任何副本。

仍会离开服务器的数据。导出为 PDF 是最明显的例子。README 将 DRAWIO_SELF_CONTAINED 描述为“设置为 1,通过 Tomcat 的 ExportProxyServlet/service/0)转发导出请求,而不是直接调用导出服务器”。反过来理解即可:默认情况下,导出请求不会留在您的部署内部。该项目还发布了 jgraph/export-server,即“draw.io 的独立图像导出服务器”,供希望在自有硬件上完成渲染的用户使用。ENABLE_DRAWIO_PROXY 默认关闭;启用后会提供一个 /proxy 端点,代表浏览器获取外部图像 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,2026 年 8 月),由此可见其中的内容很少。它没有数据库、会话存储或上传目录,因为服务器上没有需要存储的内容。

镜像页面明确说明了限制:“目前,自行托管实例不支持共享或协作功能。”界面中仍会显示这些按钮,因此有必要了解原因。实时协作需要 websocket 服务器,该服务器单独发布为 excalidraw/excalidraw-room。共享链接需要存储服务来保存加密场景。两者的地址会在构建时作为 Vite 变量(VITE_APP_WS_SERVER_URLVITE_APP_BACKEND_V2_GET_URLVITE_APP_BACKEND_V2_POST_URL)编译进资源包,仓库中的生产环境值指向 Excalidraw 自己托管的服务。Vite 会在构建期间替换这些值,因此它们最终会作为字面字符串写入 JavaScript。将这些值设置为容器环境变量不会产生任何效果,因为运行时没有代码读取它们。要让协作功能指向您自己的 room server,必须使用自定义值从源代码构建前端。规划部署前,请先确认该服务器的状态:截至 2026 年 8 月,Docker Hub 上的 excalidraw/excalidraw-room 镜像已经超过两年没有重新构建。

绘图实际存储在哪里。 场景位于浏览器的本地存储中,保存在该设备上,并按 origin 区分。在隐私窗口中打开相同 URL,画布会为空,这是最快的验证方法。清除站点数据会删除绘图,而且服务器上没有可恢复的副本。因此,应指导用户使用“保存到...”并将 .excalidraw 文件(JSON 格式)保存在会进行备份的位置。共享实例会为每个人提供一个独立的私有画布。应将其视为托管在服务器上的个人草稿本。

Kroki:代码化图表,在您的服务器上渲染

Kroki 是位于多个渲染器前的统一 HTTP 网关。您 POST 文本,它返回 SVG 或 PNG。Graphviz、PlantUML、D2 以及其他几个渲染器已内置于网关镜像中。Mermaid、BPMN 和 Excalidraw 渲染器运行在配套容器中,因此使用 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 不向主机发布任何端口,因此配套容器只能通过 Compose 网络从网关访问。这正是您需要的配置。除非调用它的 wiki 运行在另一台主机上,否则请将网关行改为 "127.0.0.1:8000:8000"。如果您以前没有在服务器上编写过 Compose 文件,请参阅在 VPS 上运行 Docker Compose,其中介绍了文件布局和 docker compose up -d 周期。

按以下顺序运行两个冒烟测试,因为它们失败的原因不同。

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 在网关内部运行,因此这里返回 SVG 文档,说明网关本身运行正常。现在测试跨容器的路径。

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 已解析且配套容器已响应。如果第一条命令成功而第二条失败,故障位于两个容器之间,因此请先查看 docker compose logs kroki,不要立即检查图表语法。

GET 形式会将图表编码到 URL 中。wiki 无需插件即可通过这种方式嵌入图像。文档提供了以下编码器。

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> 标签都可以指向该 URL。该方式存在长度上限:KROKI_MAX_URI_LENGTH 默认为 4096 字节,因此较长的图表必须通过 POST 发送。

Kroki 会读取您发送的文本,因此真正需要关注的是它的安全设置。 KROKI_SAFE_MODE 默认为 SECURE,这是三个级别中限制最严格的级别;KROKI_PLANTUML_ALLOW_INCLUDE 默认为 false。之所以设置这些默认值,是因为从渲染器的角度看,PlantUML 的 !include 指令会读取文件和 URL。在任何人都可以访问的端点上放宽这些设置,就等于向互联网提供了一个运行在容器内的文件读取器。除非您明确知道需要哪个包含路径,否则不要修改这些设置;确定后使用 KROKI_PLANTUML_INCLUDE_PATH 指定该路径。

小型 VPS 上哪一个最占内存

了解每个容器运行的内容后,资源占用顺序是可以预期的。

  • Excalidraw 镜像由 nginx 提供静态文件服务。在这三个容器中,它的开销明显最低。
  • draw.io 运行 Tomcat,即 Java 应用服务器。因此无论是否有人绘图,它都会占用 JVM(Java 虚拟机)资源。
  • Kroki 网关同样是 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 也是可行的方案,因为该网关仍会提供其中内置的所有渲染器。

这些服务都不提供用户模型,因此需要在前面加一层

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 部分很重要:如果配置有错误,reload 会保留旧配置继续运行,因此网站仍然可用,但修改不会生效。逐行解析反向代理配置介绍了此片段省略的 header 块和证书路径。

对于 Kroki,基本身份验证不是正确的工具,原因值得理解。wiki 页面使用 <img> 标签嵌入 Kroki 图片。读者的浏览器会将该 URL 作为子资源获取,并且不会向不同源发送您的凭据,因此请求会返回 401,页面上的所有图表都会显示为损坏的图片。应让 Kroki 保持在公网之外。将它放在与 wiki 容器相同的 Docker 网络中,让 wiki 通过服务名访问它,并且完全不向主机发布端口。Compose 网络如何解析服务名介绍了实现这一点的机制。

紧邻自托管 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 决定图表如何附加到页面,也决定您应选择哪种工具进行集成。

Failure modes and the strings you will see

The drawing editor opens in BookStack and spins forever. The spinner is spin=1 waiting for a handshake that never arrives. Check that embed=1&proto=json&spin=1 is present in your DRAWIO value and that the host part has no typo.

The editor frame stays blank on an HTTPS wiki. The browser console reports mixed content, loading http:// inside https://. The browser blocks the frame, and draw.io never runs. Serve the editor over HTTPS.

Kroki returns 413 Request Entity Too Large. That string comes from nginx, not from Kroki. The nginx client_max_body_size default is 1 MB and Kroki's own KROKI_MAX_BODY_SIZE default is 1mb, so a large PlantUML source hits whichever limit is lower. Raise both.

Mermaid fails while graphviz works. The gateway is healthy and the companion is not being reached. Check the service is up with docker compose ps, then check KROKI_MERMAID_HOST matches the service name, because it defaults to 127.0.0.1, which inside the gateway container means the gateway itself.

Excalidraw collaboration never connects. If you built a frontend against your own room server and put it behind nginx, the proxy has to upgrade the connection with proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade";. Without them the websocket handshake is answered as an ordinary HTTP request and the session never starts.

The canvas is empty after a browser cleanup. The scene was in local storage on that device and there is no server copy. The fix is a habit rather than a setting: export the .excalidraw file for anything worth keeping.

FAQ

自托管 draw.io 会保护我的图表隐私吗?

它会将应用程序代码保留在您的服务器上,但这与保护数据隐私是两回事。draw.io 在浏览器中渲染,因此容器根本不会保存图表。隐私取决于您将文件保存在哪里,以及您保留启用哪些出站请求。使用 ?offline=1 禁用云存储目标。还要注意,导出请求会发送到导出服务器,除非您设置 DRAWIO_SELF_CONTAINED=1 并自行运行 jgraph/export-server

为什么自托管 Excalidraw 无法进行协作?

官方镜像页面说明,自托管“不支持共享或协作功能”。实时协作需要独立的 excalidraw/excalidraw-room WebSocket 服务器,共享链接需要存储服务。这两项服务的地址会在构建时作为 Vite 变量编译到 JavaScript bundle 中,例如 VITE_APP_WS_SERVER_URL。因此,在运行中的容器上设置环境变量不会生效。使用您自己的房间服务器时,必须使用自定义值从源代码构建前端。

如何在自己的服务器上渲染 Mermaid 图表?

运行 Kroki 及其 mermaid companion 容器,并将 KROKI_MERMAID_HOST 设置为该服务的名称。然后将图表文本 POST 到 /mermaid/svg,再从响应中读取 SVG;也可以将图表编码到 GET URL 中,并将其指向 <img> 标签。该 companion 通过 Puppeteer 驱动 Chromium,因为 Mermaid 需要浏览器引擎。因此,请预留相应内存:KROKI_MERMAID_MAX_CONCURRENCY 默认同时渲染 6 个图表。

这些工具前面需要设置密码吗?

需要,因为这些工具都没有账户功能。draw.io 和 Excalidraw 会向任何找到 URL 的人提供完整编辑器,Kroki 则会渲染发送给它的任意文本。在反向代理上为这两个编辑器启用基本身份验证即可。对于 Kroki,应将其限制在与 wiki 共享的 Docker 网络中,不要发布到公网,因为读者浏览器发出的 <img> 请求不会将凭据发送到另一个源,所有嵌入的图表都会因此失效。

#diagrams#drawio#excalidraw#mermaid#kroki#Docker