1. 项目概述:为什么要把Excalidraw搬到自家服务器?
如果你经常画流程图、架构图或者需要和团队进行线上白板协作,大概率听说过或用过Excalidraw。它是一款开源的、手绘风格的在线绘图工具,界面清爽,上手极快,画出来的图自带一种随性又专业的“灵魂画手”气质,在技术圈和产品圈里特别受欢迎。但它的官方在线版本(excalidraw.com)有一个问题:所有绘图数据默认都保存在你的浏览器本地(LocalStorage)里。这意味着,一旦你清除了浏览器缓存,或者想在另一台设备上继续编辑,之前的工作就可能“消失”。虽然它也支持链接分享协作,但数据持久化和完全自主可控的需求,让很多团队和个人开始考虑本地化部署。
所谓本地化部署,就是把Excalidraw这套开源代码,从GitHub上“搬下来”,安装运行在你自己的服务器或电脑上。这样做的好处显而易见:数据完全私有,所有绘图文件都存储在你指定的地方(比如服务器硬盘、数据库或云存储);网络访问可控,在内网环境下也能高速使用,不受外网波动影响;功能可定制化,你可以根据自己的需求修改源码,比如集成内部账号系统、添加自定义图形库,或者改变保存逻辑。
最近,随着各类AI模型和工具(如DeepSeek、Minimax等)本地化部署讨论的热度攀升,大家对于“将优秀开源工具掌握在自己手中”的意愿越来越强。Excalidraw的本地化部署,正是这种理念的一个经典实践。它不涉及复杂的AI模型推理,技术栈相对清晰,是一个绝佳的、用来理解现代Web应用完整部署流程的练手项目。
接下来,我将以一个资深运维和全栈开发的视角,带你从零开始,完成一次扎实的Excalidraw本地化部署。我会详细拆解每个步骤背后的原理,分享我趟过的坑,并提供一套生产环境可用的配置方案。无论你是想搭建一个团队内部的白板工具,还是单纯想学习Node.js应用的部署,这篇文章都能给你一份可靠的“地图”。
2. 部署前的核心准备与环境解析
在动手敲命令之前,理清思路和准备好“战场”至关重要。Excalidraw的部署不是简单的文件拷贝,它是一个标准的现代前端+后端Node.js应用。
2.1 技术栈与架构理解
首先,我们得知道我们要部署的是什么。Excalidraw主要由两部分构成:
- 前端(Frontend):一个用React + TypeScript构建的单页面应用(SPA)。我们用户在浏览器里看到的绘图界面、交互逻辑全都属于这部分。在部署时,这部分代码会被打包工具(如Webpack、Vite)编译、压缩,生成一堆静态文件(HTML、CSS、JS)。
- 后端(Backend):一个Node.js服务器。它主要提供两个关键服务:
- 静态文件服务:把上面打包好的前端文件,通过HTTP发送给浏览器。
- 协作服务器(可选但重要):一个WebSocket服务,用于实现多人在线实时协作编辑同一个绘图。这是Excalidraw体验的核心之一。
在官方仓库里,这两部分代码是放在一起的,但通过不同的构建脚本和启动命令来区分。理解这个“前后端分离但同仓”的结构,能帮助我们在部署时做出正确的决策。
2.2 服务器与工具选型
你需要准备一台服务器。可以是:
- 云服务器:如阿里云、腾讯云、AWS的ECS/EC2,选择Ubuntu 20.04/22.04 LTS或CentOS 7/8(注:CentOS 7已停止维护,建议用Rocky Linux或AlmaLinux替代)。1核2G内存是起步配置,小团队使用完全足够。
- 本地电脑/虚拟机:用于测试。确保系统是Linux或macOS(Windows也可通过WSL2运行Linux环境)。
- 容器环境:如果你熟悉Docker,那么用Docker部署是最干净、最一致的方式。我会同时介绍传统部署和Docker部署两种方法。
必需的软件环境:
- Node.js:Excalidraw要求Node.js版本 >= 18.x。这是运行后端和构建前端的引擎。
- npm 或 yarn 或 pnpm:Node.js的包管理器,用于安装依赖。我推荐使用
pnpm,速度更快,磁盘空间占用更少。 - Git:用于从GitHub克隆代码。
- Nginx(推荐):一个高性能的HTTP和反向代理服务器。我们将用它作为“门户”,处理外部访问、SSL加密(HTTPS)、负载均衡(如果需要)以及更优雅地服务前端静态文件。直接用Node.js服务前端静态文件也可以,但Nginx更专业、性能更好。
注意:在选择Node.js版本时,务必确认版本号。使用过旧版本(如Node.js 12, 14)会导致依赖安装失败或运行时错误。可以使用
nvm(Node Version Manager)工具来轻松安装和切换多个Node.js版本,这是管理Node.js环境的最佳实践。
2.3 域名与网络规划
如果你希望从公网访问你的Excalidraw,需要一个域名(例如draw.yourcompany.com)。如果没有,可以直接用服务器IP地址访问,但这不利于记忆,且无法配置HTTPS证书(部分浏览器会对非HTTPS的WebSocket连接有限制)。
你需要:
- 购买一个域名,并在域名管理后台添加一条A记录,指向你的服务器公网IP。
- 在服务器防火墙(如
ufw或云服务器的安全组)中,开放必要的端口。通常需要:80端口:用于HTTP流量。443端口:用于HTTPS流量。3000或你自定义的端口:用于Node.js后端服务(如果Nginx反向代理配置正确,此端口可以不对外开放,仅限内部访问,更安全)。
3. 两种主流部署方案详解与实操
我将详细介绍两种部署方式:基于PM2的传统部署和基于Docker的容器化部署。你可以根据团队的技术栈和运维习惯来选择。
3.1 方案一:基于PM2的传统部署(直接运行)
这种方案更贴近源码,适合需要频繁自定义修改、调试的场景。
3.1.1 获取源代码与安装依赖
首先,通过SSH连接到你的服务器。
# 1. 更新系统包并安装基础工具 sudo apt update && sudo apt upgrade -y sudo apt install -y git curl # 2. 安装Node.js(使用NodeSource仓库安装指定版本,这里以18.x为例) curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs # 3. 安装pnpm(如果你偏好npm或yarn,可相应调整) sudo npm install -g pnpm # 4. 克隆Excalidraw官方仓库(克隆到合适目录,如 /var/www/) sudo mkdir -p /var/www cd /var/www sudo git clone https://github.com/excalidraw/excalidraw.git cd excalidraw # 5. 安装项目依赖(这个过程可能需要几分钟,取决于网络和服务器性能) sudo pnpm install实操心得:在服务器上安装依赖时,可能会遇到网络超时或某些包下载慢的问题。可以考虑配置pnpm或npm的国内镜像源(如淘宝镜像)。对于
pnpm,可以执行:pnpm config set registry https://registry.npmmirror.com。此外,sudo pnpm install可能会因为权限问题导致某些包链接失败。更好的做法是创建一个专用系统用户来运行应用,避免全程使用root。
3.1.2 构建生产环境前端代码
Excalidraw的前端代码需要经过构建(Build)才能用于生产环境。构建过程会进行代码压缩、混淆、Tree Shaking等优化。
# 在项目根目录下执行构建命令 sudo pnpm build:app:prod这个命令会执行一系列操作,最终在项目根目录下生成一个build/文件夹。这个文件夹里就是打包好的、可以直接被浏览器运行的静态文件。你可以通过ls -la build/查看生成的文件。
3.1.3 配置环境变量与启动后端
Excalidraw的后端服务可以通过环境变量进行配置。我们需要创建一个配置文件或直接在启动命令中设置。
一个关键的配置是PORT,它指定后端服务监听的端口。我们创建一个简单的启动脚本:
# 在项目根目录创建一个启动脚本 start.sh sudo nano start.sh在编辑器中输入以下内容:
#!/bin/bash # 设置环境变量:后端服务运行在3001端口,前端构建目录为当前目录下的build export PORT=3001 export NODE_ENV=production # 启动后端服务器,并指定静态文件目录为 ./build node packages/excalidraw-app/express-app.js保存并退出(按Ctrl+X,然后按Y,再按Enter)。给脚本添加执行权限并运行:
sudo chmod +x start.sh sudo ./start.sh此时,你应该能在终端看到服务器启动的日志,监听在3001端口。打开浏览器,访问http://你的服务器IP:3001,应该能看到Excalidraw的界面了。
但是,直接这样运行,终端关闭服务就停了。我们需要一个进程守护工具。
3.1.4 使用PM2进行进程守护
PM2是一个强大的Node.js进程管理器,可以保证应用持续运行,并在崩溃时自动重启。
# 全局安装PM2 sudo pnpm install -g pm2 # 使用PM2启动我们的应用 # 假设我们仍在项目根目录,且start.sh脚本已配置好 cd /var/www/excalidraw pm2 start ./start.sh --name excalidraw-server # 设置PM2开机自启 pm2 startup # 执行上面命令后,PM2会给出一个类似 `sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u your_user --hp /home/your_user` 的命令,复制并执行它。 pm2 save现在,你的Excalidraw后端服务就在PM2的守护下稳定运行了。你可以通过pm2 status查看状态,pm2 logs excalidraw-server查看日志。
3.2 方案二:基于Docker的容器化部署
容器化部署是当前的主流,它能提供完全一致的环境,极大简化了“在我机器上能跑”的问题。
3.2.1 编写Dockerfile
Excalidraw官方没有提供正式的Docker镜像,但我们可以自己编写Dockerfile来构建。在项目根目录创建Dockerfile:
# 使用官方Node.js LTS版本作为基础镜像 FROM node:18-alpine AS builder # 安装pnpm RUN npm install -g pnpm # 设置工作目录 WORKDIR /app # 复制包管理文件和源代码 COPY package.json pnpm-lock.yaml ./ COPY . . # 安装依赖并构建生产版本 RUN pnpm install --frozen-lockfile RUN pnpm build:app:prod # 第二阶段:运行阶段 FROM node:18-alpine AS runner WORKDIR /app # 从构建阶段复制构建产物和必要的运行文件 COPY --from=builder /app/build ./build COPY --from=builder /app/package.json ./ COPY --from=builder /app/packages/excalidraw-app/express-app.js ./express-app.js # 安装生产依赖(如果需要,这里可以只安装express等运行时依赖,但Excalidraw的express-app.js可能依赖项目根node_modules) # 更简单的做法是复制整个node_modules,但为了镜像最小化,我们可以尝试只安装必要包。 # 这里我们采用一个折中方案:复制构建阶段安装的所有依赖(因为构建阶段已经安装了全部) COPY --from=builder /app/node_modules ./node_modules # 设置环境变量 ENV NODE_ENV=production ENV PORT=80 # 暴露端口 EXPOSE 80 # 启动命令 CMD ["node", "express-app.js"]这个Dockerfile采用了“多阶段构建”策略。第一阶段(builder)负责安装所有依赖并执行构建,生成build文件夹。第二阶段(runner)使用一个干净的Node.js环境,只复制构建产物和运行时必需的依赖,从而得到一个体积更小的最终镜像。
3.2.2 构建镜像与运行容器
在包含Dockerfile的目录下执行:
# 构建Docker镜像,命名为 excalidraw:latest docker build -t excalidraw:latest . # 运行容器 # -d: 后台运行 # -p 3000:80: 将宿主机的3000端口映射到容器的80端口 # --name excalidraw: 给容器命名 docker run -d -p 3000:80 --name excalidraw excalidraw:latest现在,访问http://你的服务器IP:3000就能看到运行在Docker容器中的Excalidraw了。管理容器可以使用docker ps、docker logs excalidraw、docker stop/start excalidraw等命令。
注意事项:Docker方式默认将所有数据(如图形文件,如果后端有存储逻辑的话)保存在容器内部。容器被删除,数据也会丢失。对于生产环境,必须通过
-v参数将容器内的数据目录挂载到宿主机的持久化存储上。但需要注意的是,Excalidraw的协作服务器默认使用内存存储会话,绘图数据默认保存在浏览器本地。如果你需要服务端持久化,需要修改后端代码或寻找支持后端存储的社区版本/插件,这超出了基础部署的范围。
4. 生产环境进阶配置:Nginx、HTTPS与性能优化
让服务在3000或3001端口跑起来只是第一步。要用于生产,我们还需要配置域名、HTTPS加密,并用Nginx作为反向代理来提升安全性和性能。
4.1 安装与配置Nginx
# Ubuntu/Debian系统安装Nginx sudo apt install -y nginx安装后,Nginx会自动启动。我们需要为其创建一个站点配置文件。
sudo nano /etc/nginx/sites-available/excalidraw写入以下配置(请将draw.yourdomain.com替换为你的真实域名,将proxy_pass的端口改为你的Node.js服务实际端口,如3001或3000):
server { listen 80; server_name draw.yourdomain.com; # 你的域名 # 前端静态文件由Nginx直接服务,效率更高 location / { # 如果你的静态文件在 /var/www/excalidraw/build root /var/www/excalidraw/build; index index.html; try_files $uri $uri/ /index.html; # 支持React Router等SPA路由 } # 反向代理WebSocket协作服务 location /socket.io/ { proxy_pass http://localhost:3001; # 指向你的Node.js协作服务器 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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; } # 也可以选择将所有API/后端请求都代理(如果后端还有其他接口) # location /api/ { # proxy_pass http://localhost:3001; # ...其他proxy_set_header... # } }关键解析:这个配置是精华所在。
root /var/www/excalidraw/build;:让Nginx直接服务前端静态文件,比Node.js的express.static中间件性能更高,能减轻Node.js进程的负担。try_files $uri $uri/ /index.html;:这是单页面应用(SPA)的标配。当用户直接访问一个前端路由(如/room/abc123)时,Nginx会先尝试找对应的文件,找不到则返回index.html,由前端的JavaScript路由来处理。location /socket.io/:这是配置WebSocket反向代理的关键。协作功能依赖WebSocket长连接,Nginx必须正确配置Upgrade和Connection头,才能将HTTP协议升级为WebSocket协议。
启用该站点配置并测试:
# 创建软链接到sites-enabled目录 sudo ln -s /etc/nginx/sites-available/excalidraw /etc/nginx/sites-enabled/ # 测试Nginx配置语法是否正确 sudo nginx -t # 如果显示“syntax is ok”,则重载Nginx使配置生效 sudo systemctl reload nginx现在,你应该可以通过域名(或服务器IP)的80端口访问了,并且协作功能正常。
4.2 配置HTTPS(SSL/TLS证书)
没有HTTPS的网站是不完整的,而且现代浏览器对非HTTPS站点的WebSocket连接可能有限制。我们使用Let‘s Encrypt的免费证书,并通过Certbot工具自动化获取和续签。
# 安装Certbot和Nginx插件 sudo apt install -y certbot python3-certbot-nginx # 运行Certbot,自动为你的域名配置SSL sudo certbot --nginx -d draw.yourdomain.com按照提示操作(输入邮箱、同意服务条款等),Certbot会自动完成以下工作:
- 验证你对域名的所有权(通常通过添加一个临时HTTP文件)。
- 从Let‘s Encrypt获取SSL证书。
- 自动修改你的Nginx配置文件,添加443端口监听和SSL相关配置。
- 设置自动证书续签(通过systemd timer)。
完成后,你的Nginx配置文件中会自动添加类似下面的内容,并重定向HTTP到HTTPS:
server { listen 443 ssl http2; server_name draw.yourdomain.com; ssl_certificate /etc/letsencrypt/live/draw.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/draw.yourdomain.com/privkey.pem; # ... 其他SSL优化配置由Certbot自动添加 ... # 你的原有location配置在这里 location / { root /var/www/excalidraw/build; ... } ... } # HTTP强制跳转到HTTPS server { listen 80; server_name draw.yourdomain.com; return 301 https://$server_name$request_uri; }现在,访问http://draw.yourdomain.com会自动跳转到https://draw.yourdomain.com,并且浏览器地址栏会显示安全锁标志。
4.3 性能与安全优化建议
- 静态资源缓存:在Nginx配置中为
build/static目录下的JS、CSS、图片等文件设置长期缓存,利用浏览器缓存加速重复访问。location /static/ { root /var/www/excalidraw/build; expires 1y; add_header Cache-Control "public, immutable"; } - 限制请求体大小:在Nginx中设置
client_max_body_size,防止过大的请求攻击。 - 隐藏Nginx版本信息:在
nginx.conf的http块中设置server_tokens off;。 - PM2集群模式:如果你的服务器是多核CPU,可以使用PM2的集群模式启动多个Node.js实例,充分利用CPU资源。
pm2 start start.sh -i max --name excalidraw-server。注意,这要求你的应用是无状态的,对于Excalidraw协作服务器,需要确保其支持多实例间的状态同步(默认的内存存储不支持,需要引入Redis等外部存储,这属于高级定制)。 - 日志管理:配置PM2和Nginx的日志轮转(logrotate),防止日志文件撑满磁盘。
5. 常见问题排查与维护技巧
即使按照步骤操作,也可能会遇到问题。这里记录了一些我亲自踩过的坑和解决方案。
5.1 构建失败或依赖安装错误
- 问题:
pnpm install或pnpm build过程中报错,提示某个包找不到或网络错误。 - 排查:
- 网络问题:检查服务器能否正常访问
registry.npmjs.org或github.com。可以尝试切换国内镜像源。 - Node.js版本不符:确认Node.js版本是否为18.x或更高。使用
node -v检查。 - 权限问题:避免在root目录下或用root权限安装依赖。建议为项目创建一个专用用户。
- 缓存问题:尝试清除pnpm缓存:
pnpm store prune,然后重新安装。
- 网络问题:检查服务器能否正常访问
5.2 访问网站显示空白页或404
- 问题:通过Nginx访问域名,页面空白或显示“404 Not Found”。
- 排查:
- Nginx root路径错误:检查Nginx配置中
root指令指向的路径是否正确,是否有build文件夹及其内容。确保Nginx进程有读取该目录的权限(通常用户是www-data或nginx)。 - SPA路由问题:确认Nginx配置中包含了
try_files $uri $uri/ /index.html;这一行。没有它,直接访问子路由就会404。 - 检查Nginx错误日志:
sudo tail -f /var/log/nginx/error.log,这里通常有更详细的错误信息。
- Nginx root路径错误:检查Nginx配置中
5.3 协作功能(实时同步)失效
- 问题:可以打开画板,但多人协作时,一个人的操作无法同步到其他人的屏幕上。
- 排查:
- WebSocket代理配置:这是最常见的原因。确保Nginx配置中包含了正确的
/socket.io/的location块,并且proxy_pass的端口与后端Node.js服务监听的端口一致。配置中必须有proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";。 - HTTPS/WSS:如果你的站点是HTTPS,WebSocket连接会自动使用WSS(WebSocket Secure)。确保Nginx的SSL配置正确,且后端服务(如果直接暴露)也支持WSS。在我们的架构中,Nginx处理SSL,后端是HTTP,所以只需确保Nginx代理配置正确。
- 防火墙/安全组:确保服务器的安全组或防火墙允许80/443端口的入站流量。如果Node.js服务端口(如3001)直接对外暴露,也需要放行,但最佳实践是不暴露它。
- 检查后端服务日志:通过
pm2 logs或docker logs查看Node.js后端是否有关于Socket.io的错误信息。
- WebSocket代理配置:这是最常见的原因。确保Nginx配置中包含了正确的
5.4 如何更新到新版本?
Excalidraw项目更新活跃。更新部署的步骤:
- 传统部署更新:
cd /var/www/excalidraw git pull origin master pnpm install # 或 pnpm update, 确保依赖更新 pnpm build:app:prod pm2 restart excalidraw-server - Docker部署更新:
cd /path/to/Dockerfile git pull origin master docker build -t excalidraw:latest . # 重新构建镜像 docker stop excalidraw docker rm excalidraw docker run -d -p 3000:80 --name excalidraw excalidraw:latest # 重新运行容器提示:使用Docker Compose可以更方便地管理更新流程。也可以给镜像打上版本标签而非总是
latest,便于回滚。
5.5 数据持久化与备份
默认配置下,绘图数据保存在用户浏览器本地。这意味着:
- 优势:隐私性好,服务器无状态,压力小。
- 劣势:用户换设备或清缓存会丢失文件;团队无法集中管理资产。
如果需要服务端存储,你需要:
- 研究Excalidraw的后端存储接口。社区有一些方案,例如修改
express-app.js,将房间数据和绘图数据保存到数据库(如PostgreSQL、MongoDB)或对象存储(如AWS S3、MinIO)。 - 这是一个深度定制化的过程,需要你熟悉Node.js和Excalidraw的代码结构。你可以搜索 “excalidraw self-hosted storage” 寻找开源方案或灵感。
对于部署本身,你的备份重点应该是:
- 服务器系统镜像:定期为云服务器创建快照。
- Nginx和PM2配置文件:将
/etc/nginx/sites-available/excalidraw和PM2的启动脚本进行备份。 - Dockerfile和构建上下文:如果你用Docker,备份整个包含Dockerfile的目录。
- SSL证书:Let‘s Encrypt证书在
/etc/letsencrypt/live/下,但Certbot会自动续签,通常无需手动备份。可以备份整个/etc/letsencrypt/目录以防万一。
整个部署过程,从环境准备到生产级配置,核心在于理解每个组件(Node.js, Nginx, Docker)的角色和它们之间的协作关系。本地化部署Excalidraw不仅仅是为了得到一个可用的白板工具,更是一次对现代Web应用部署全链路的宝贵实践。当你看到通过自己的配置,一个功能完整、性能可靠、安全加密的Excalidraw服务稳定运行时,那种对系统掌控感带来的满足,是直接用SaaS服务无法比拟的。