为什么要在自己的服务器上部署 Excalidraw
平时写技术文章、画系统架构图,我用的比较多的是 draw.io 和 Excalidraw。draw.io 功能全,但界面偏正式;Excalidraw 的手绘风格看起来更轻松,画示意图、时序图、简单的流程图很顺手,导出的图片也干净。
官方站点 excalidraw.com 可以直接用,不需要注册。但它有两个问题:一是数据存在浏览器本地,换台电脑就看不到了;二是分享链接虽然可以协作,但链接一旦泄露,任何人都能进来改。对于团队内部用,还是希望有一个自己控制的地址。
Excalidraw 是开源的,源码在 GitHub 上(MIT 协议),官方也提供了 Docker 镜像。部署本身不难,真正花时间的其实是后面那一步------让它有一个固定的公网地址,能通过域名访问,并且带上 HTTPS。这篇文章就把这两步完整走一遍。
需要说明的是,Excalidraw 分为两部分:前端(画板本身)和协作后端(一个叫 excalidraw-room 的 WebSocket 服务)。如果你只需要自己画图、导出,只部署前端就够了;如果要多人实时协作,才需要额外部署 room 服务。本文主要讲前端部署和公网访问,协作服务放在最后简单提一下。
环境准备
我用的是一台 2 核 2G 的云服务器,系统是 Ubuntu 22.04。这个配置跑 Excalidraw 前端绰绰有余,因为它本质上是一堆静态文件,加一个轻量的 Web 服务器就够了。
需要提前准备的东西:
- 一台有公网 IP 的服务器
- 一个已经解析到这台服务器 IP 的域名(后面配 HTTPS 要用)
- 服务器上装好 Docker 和 Docker Compose
Docker 的安装这里不展开,官方文档写得很清楚。装完之后用下面两条命令确认一下:
bash
docker --version
docker compose version
我用的是 Docker 24.x 和 Compose v2,如果你的版本差异比较大,命令格式可能有区别,注意一下 docker-compose(带横杠)和 docker compose(不带横杠)这两种写法。
选哪个镜像

这里是我第一个需要判断的地方。Excalidraw 官方的 Docker 镜像情况有点绕,我查了一下,大致有这几种选择:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
官方 excalidraw/excalidraw 镜像 |
官方维护,版本跟源码同步 | 镜像体积偏大,默认配置需要自己调整 | 想跟官方保持一致 |
| 第三方构建的镜像 | 有些做了体积优化 | 更新不及时,来源不好确认 | 不太推荐 |
| 自己从源码构建 | 完全可控 | 需要 Node 环境和构建流程,耗时 | 需要改源码定制 |
我最后用的是官方镜像 excalidraw/excalidraw。拉取命令:
bash
docker pull excalidraw/excalidraw:latest
【注意】:latest 标签在正式环境里其实不太推荐,因为它会在你重新拉取时悄悄换版本。稳妥的做法是先拉下来看看当前是哪个版本,然后固定住。可以这样查:
bash
docker inspect excalidraw/excalidraw:latest --format '{{index .Config.Labels "org.opencontainers.image.version"}}'
如果这个 label 没有,就进容器看一下构建信息。我这边拉下来的版本是 0.17.x 系列,具体小版本号会随时间变,你部署的时候以实际拉到的为准。
先用最简单的方式跑起来
在正式配置之前,我习惯先用最少的参数把容器跑起来,确认镜像本身没问题。
bash
docker run -d \
--name excalidraw \
-p 8080:80 \
--restart unless-stopped \
excalidraw/excalidraw:latest
这里几个参数说明一下:
-p 8080:80:把容器内的 80 端口映射到宿主机的 8080。Excalidraw 官方镜像内部用的是 Nginx,监听 80。如果你宿主机 8080 被占了,换成别的。--restart unless-stopped:服务器重启后容器自动起来,除非你手动停过它。这个对长期运行的服务很实用。-d:后台运行。
跑起来之后访问 http://你的服务器IP:8080,应该能看到 Excalidraw 的界面。如果打不开,先看容器日志:
bash
docker logs excalidraw
常见的问题是端口冲突,日志里会直接报。另一个可能是云服务器的安全组没放行 8080,这个要去控制台里加规则,和 Docker 本身无关。
到这里,白板已经能用了。但 IP 加端口的形式不好记,也不方便分享,接下来处理公网访问。
用 Docker Compose 管理配置

直接 docker run 适合验证,长期用还是 Compose 更清楚。我在服务器上建了一个目录 /opt/excalidraw,里面放 docker-compose.yml:
yaml
services:
excalidraw:
image: excalidraw/excalidraw:latest
container_name: excalidraw
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
注意这里端口映射写的是 127.0.0.1:8080:80,而不是 8080:80。区别在于:前者只允许本机访问,外部访问不进来;后者会监听所有网卡。因为后面我要用 Nginx 做反向代理,容器只需要对 Nginx 可见就行,不需要直接暴露到公网。这样能少一层攻击面。
启动:
bash
cd /opt/excalidraw
docker compose up -d
用 docker compose ps 确认状态是 running。
配置 Nginx 反向代理

现在容器在本机 8080 上跑着,接下来让 Nginx 把域名请求转发过去。
先装 Nginx:
bash
sudo apt update
sudo apt install -y nginx
然后在 /etc/nginx/sites-available/ 下新建一个配置文件,比如叫 excalidraw.conf:
nginx
server {
listen 80;
server_name draw.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
把 draw.example.com 换成你自己的域名。几个 header 的作用:
Host:让后端知道原始请求的域名,有些应用会根据它生成链接。X-Real-IP和X-Forwarded-For:传递真实客户端 IP,方便排查。X-Forwarded-Proto:告诉后端原始请求是 HTTP 还是 HTTPS,后面配了证书会用到。
启用配置:
bash
sudo ln -s /etc/nginx/sites-available/excalidraw.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx -t 是检查语法,一定要跑,通过了再 reload。
这时候访问 http://draw.example.com 应该就能打开白板了。前提是你的域名已经解析到服务器 IP,这一步在域名服务商那边配置 A 记录。
加上 HTTPS
只用 HTTP 的话,浏览器会提示不安全,而且 Excalidraw 的一些功能(比如剪贴板 API)在非安全上下文下可能受限。所以证书这一步建议做。
我用的是 Certbot,它会自动改 Nginx 配置:
bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d draw.example.com
Certbot 会引导你填邮箱、同意条款,然后自动申请 Let's Encrypt 证书,并把上面的 Nginx 配置改成 443 监听、自动跳转。完成后它会提示证书路径和过期时间。
【踩坑提醒】Certbot 自动改配置的时候,如果你的 server 块里有自定义的 location,有时候会被改乱。改完之后建议再看一眼 /etc/nginx/sites-available/excalidraw.conf,确认 proxy_pass 那几行还在。
证书是 90 天有效期,Certbot 装的时候会自动配一个 systemd timer 来续期。可以手动测试一下:
bash
sudo certbot renew --dry-run
没有报错就说明自动续期是正常的。
到这一步,一个带 HTTPS 的固定公网地址就配好了。
数据存在哪里

这一点需要说清楚,因为它影响你怎么用。
Excalidraw 前端本身是纯静态的,默认不保存任何数据到服务器。你在画板上画的东西,是存在浏览器本地的 IndexedDB 里的。也就是说:
- 换一台电脑、换一个浏览器,就看不到之前画的内容。
- 清除浏览器数据,画的内容也会丢。
- 服务器上没有任何你的图,Nginx 日志除外。
那怎么保存?两个办法:
- 画完点左上角菜单,导出成
.excalidraw文件或 PNG/SVG。文件存在哪由你决定。 - 用"分享"功能生成协作链接,这个链接会把内容编码在 URL 的
#后面,任何人拿到链接都能打开。但这是只读还是可编辑,取决于你选的模式。
如果你想要"登录后自动云端保存",那需要自己接后端存储,Excalidraw 官方没有提供这个功能。社区里有 excalidraw-room 用于协作,但它是实时的,不负责持久化。
这一点我没有验证过用第三方存储方案的效果,所以不展开。如果你只是自己画图、导出使用,默认方式完全够。
关于协作服务 excalidraw-room
如果你的需求是多人同时在一个白板上画,那需要额外部署 excalidraw-room。它是一个独立的 WebSocket 服务,官方仓库是 excalidraw/excalidraw-room。
大致流程是:
- 用 Docker 或 Node 把 room 服务跑起来,它默认监听 3002 端口。
- 在前端配置里把协作服务地址指向这个 room 服务。
- 通过 Nginx 把 WebSocket 请求代理过去。
这里有个关键点:Nginx 代理 WebSocket 需要额外的 header,普通 HTTP 代理配置不行:
nginx
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
Upgrade 和 Connection 这两行是 WebSocket 握手必需的,少了会一直连不上,浏览器控制台会报 400 或连接失败。
【注意】前端要连到你自建的 room 服务,通常需要重新构建前端镜像,把环境变量指向你的地址。官方镜像能不能通过运行时配置改这一点,我没有验证,所以不给出具体做法。如果你的团队协作需求不强烈,用分享链接的方式其实更省事。
几个容易出问题的地方
整理一下我实际遇到的或者能预见的坑:
1. 端口映射写成了 8080:80 而不是 127.0.0.1:8080:80
前者会把 8080 暴露到公网,如果服务器防火墙没配好,别人可以直接绕过 Nginx 访问。虽然 Excalidraw 本身没什么敏感数据,但少暴露一个端口总是好的。
2. 域名解析没生效就去申请证书
Certbot 需要验证你对域名的控制权,如果 A 记录还没生效,会直接失败。可以先 ping draw.example.com 看看解析对不对。
3. 云服务器安全组没放行 80 和 443
这是最常见的"配置都对但就是打不开"的原因。Nginx 在跑、容器在跑、域名也解析了,但请求根本到不了服务器。去云控制台的安全组里确认 80 和 443 是放行的。
4. 容器内路径和宿主机路径搞混
Excalidraw 官方镜像没有需要持久化的数据卷,所以不用挂载目录。如果你看到别的教程让你挂载 /usr/share/nginx/html,那多半是想改静态文件,普通使用不需要。
5. 升级镜像后配置丢失
因为数据都在浏览器端,升级镜像不会丢图。但如果你之前手动改过容器里的文件,重新拉镜像会覆盖。用 Compose 管理的话,配置都在 yml 里,不会丢。
最终的目录和配置
整理一下最后的样子:
/opt/excalidraw/
└── docker-compose.yml
/etc/nginx/sites-available/
└── excalidraw.conf
docker-compose.yml:
yaml
services:
excalidraw:
image: excalidraw/excalidraw:latest
container_name: excalidraw
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
Nginx 配置(Certbot 改过之后大概是这个形式):
nginx
server {
listen 443 ssl;
server_name draw.example.com;
ssl_certificate /etc/letsencrypt/live/draw.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/draw.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name draw.example.com;
return 301 https://$host$request_uri;
}
维护的时候,升级只需要:
bash
cd /opt/excalidraw
docker compose pull
docker compose up -d
一点使用上的建议
部署完之后我用了几天,有几个感受。
Excalidraw 的定位就是轻量画图,不要指望它当知识库用。它不存服务器数据这个设计,对个人使用是优点------没有账号、没有隐私顾虑;但对团队就是缺点------没法共享素材库。如果你的团队需要共享图库,要么用它的分享链接,要么就得自己接后端,成本不低。
另外,如果你只是想临时画个图,其实没必要自己部署。官方站点够用,导出文件也是本地的。自己部署的价值主要在于:固定的访问地址、HTTPS、以及数据完全在自己手里(虽然默认情况下数据其实在浏览器里)。想清楚这一点,再决定要不要折腾。
如果你的场景里需要把 Excalidraw 集成到自己的系统里,比如嵌入到某个后台页面,官方提供了 npm 包 @excalidraw/excalidraw,可以当 React 组件用。这条路和 Docker 部署是两条完全不同的路线,适合有前端开发能力的团队,这里就不展开了。