Excalidraw是一款备受欢迎的开源手绘风格白板工具,支持实时协作和多种图形导出格式。对于团队来说,将其部署到自有服务器上既能保护数据隐私,又能灵活控制访问权限。容器化部署是目前最推荐的方案,它将Excalidraw及其运行环境封装在Docker容器中,避免了宿主机环境污染和版本冲突问题。

部署前的环境准备与依赖分析
在开始容器化之前,我们需要充分了解Excalidraw的技术栈和运行方式。Excalidraw本质上是一个基于React构建的前端单页应用,源码托管在GitHub上。它使用Node.js作为构建环境,通过yarn管理依赖包,最终编译生成纯静态资源文件。在生产环境中运行时,只需要一个Web服务器(如Nginx)来托管这些静态文件即可,并不需要Node.js运行时。
对于实时协作功能,Excalidraw依赖WebSocket进行通信。如果只需要单用户使用,部署前端静态文件就够了。但如果需要多人实时协作,则需要额外部署一个协作后端服务。官方提供了基于Socket.IO的协作服务器,可以与Redis配合实现会话管理。在容器化时,我们可以将这些组件分别打包为独立容器,通过Docker网络互联。
服务器环境方面,建议使用Docker Engine 20.10及以上版本,同时安装Docker Compose V2用于多容器编排。操作系统可以选择Ubuntu 22.04 LTS或CentOS Stream等主流Linux发行版。服务器配置方面,2核CPU、2GB内存、20GB磁盘空间足以支撑中小型团队(50人以下)的日常使用。如果需要公网访问,还需确保防火墙开放80和443端口,并准备好域名和SSL证书。
编写Dockerfile构建高效镜像
构建Excalidraw镜像的核心思路是采用多阶段构建(Multi-stage Build)。第一阶段使用Node.js官方镜像编译源码生成静态资源,第二阶段使用Nginx Alpine镜像托管这些静态文件。这种方式可以显著减小最终镜像体积,避免将Node.js运行时、npm缓存和源码文件带入生产环境,最终镜像通常只有几十MB。
下面是完整的Dockerfile示例:
# 第一阶段:构建阶段 FROM node:18-alpine AS builder WORKDIR /app # 安装git用于克隆源码 RUN apk add --no-cache git # 克隆Excalidraw官方仓库 RUN git clone https://github.com/excalidraw/excalidraw.git . # 安装项目依赖 RUN yarn install --frozen-lockfile # 执行生产环境构建 RUN yarn build # 第二阶段:运行阶段 FROM nginx:alpine # 设置时区 RUN apk add --no-cache tzdata ENV TZ=Asia/Shanghai # 复制构建产物到Nginx默认目录 COPY --from=builder /app/build /usr/share/nginx/html # 复制自定义Nginx配置 COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 # 健康检查 HEALTHCHECK --interval=30s --timeout=3s \ CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1 CMD ["nginx", "-g", "daemon off;"]
接下来需要准备Nginx配置文件,用于正确处理前端路由和WebSocket代理。Excalidraw使用客户端路由,所有路径都需要回退到index.html,否则刷新页面会出现404错误:
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 开启gzip压缩
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
# 前端路由处理
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# WebSocket代理(协作功能)
location /socket {
proxy_pass http://collab:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}
多阶段构建的优势非常明显。第一阶段镜像可能超过1GB,包含完整的Node.js运行时和所有devDependencies,但这个镜像只是中间产物,不会出现在最终结果中。第二阶段只包含Nginx和静态文件,体积通常在50MB左右。构建过程中需要注意yarn.lock文件的存在性,确保依赖版本一致性。如果在国内网络环境下构建,建议在yarn install之前添加yarn config set registry https://registry.npmmirror.com来加速依赖安装。
使用Docker Compose实现一键编排部署
单容器运行虽然简单,但难以管理多组件协作和数据持久化。使用Docker Compose可以将Excalidraw前端、协作后端、Redis等组件统一编排,实现一键启动、停止和更新。下面是一个适用于生产环境的完整编排方案:
version: "3.8"
services:
excalidraw:
build:
context: .
dockerfile: Dockerfile
container_name: excalidraw-web
ports:
- "8080:80"
restart: unless-stopped
depends_on:
- collab
- redis
environment:
- REACT_APP_BACKEND_V2_API_URL=http://draw.ipipp.com
- REACT_APP_SOCKET_SERVER_URL=ws://draw.ipipp.com
deploy:
resources:
limits:
cpus: "1.0"
memory: 256M
networks:
- excalidraw-net
collab:
image: excalidraw/excalidraw-room:latest
container_name: excalidraw-collab
restart: unless-stopped
environment:
- REDIS_URL=redis://redis:6379
- PORT=3000
depends_on:
- redis
deploy:
resources:
limits:
cpus: "0.5"
memory: 128M
networks:
- excalidraw-net
redis:
image: redis:7-alpine
container_name: excalidraw-redis
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
restart: unless-stopped
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
networks:
- excalidraw-net
volumes:
redis-data:
networks:
excalidraw-net:
driver: bridge
上述Compose文件定义了三个服务。excalidraw是前端Web服务,监听宿主机8080端口;collab是协作后端,基于Socket.IO实现实时通信;redis用于存储协作会话数据和房间状态。三个容器通过excalidraw-net桥接网络互联,前端容器通过Nginx配置中的proxy_pass http://collab:3000将WebSocket请求转发到协作后端。
在资源限制方面,通过deploy.resources.limits为每个容器设置了CPU和内存上限。前端容器限制1核CPU和256MB内存,协作后端和Redis各限制0.5核CPU和128-256MB内存。这些限制防止单个容器在异常情况下占用过多宿主机资源。Redis配置了appendonly持久化和LRU淘汰策略,在内存不足时自动清理旧数据,保证服务稳定性。
启动服务只需执行docker compose up -d命令。首次启动会自动构建Excalidraw镜像并拉取协作后端和Redis镜像。查看日志可以使用docker compose logs -f,停止服务使用docker compose down。如果只更新Excalidraw版本,执行docker compose build excalidraw && docker compose up -d excalidraw即可滚动更新前端,不影响协作后端和Redis的运行。
反向代理配置与HTTPS支持
公网部署Excalidraw必须配置HTTPS。浏览器的Service Worker、IndexedDB和WebSocket等API要求安全上下文(Secure Context),HTTP环境下部分功能会受限甚至不可用。使用Nginx作为反向代理是最常见的方案,它同时负责SSL终止和请求转发。
# HTTP跳转HTTPS
server {
listen 80;
server_name draw.ipipp.com;
return 301 https://$host$request_uri;
}
# HTTPS主配置
server {
listen 443 ssl http2;
server_name draw.ipipp.com;
ssl_certificate /etc/letsencrypt/live/draw.ipipp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/draw.ipipp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 安全相关响应头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000" always;
client_max_body_size 50m;
# 前端代理
location / {
proxy_pass http://excalidraw-web:80;
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;
}
# WebSocket代理
location /socket {
proxy_pass http://excalidraw-collab:3000;
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_read_timeout 86400;
proxy_send_timeout 86400;
}
}
配置反向代理时最容易踩坑的就是WebSocket代理。必须添加Upgrade和Connection两个请求头,将HTTP连接升级为WebSocket连接,否则实时协作功能完全无法工作。proxy_read_timeout设置为86400秒(24小时)是为了避免长时间空闲的WebSocket连接被Nginx主动断开,默认60秒超时会导致用户短暂离开后协作断线。
SSL证书管理推荐使用Certbot自动申请和续期Let's Encrypt免费证书。可以将Certbot也容器化,通过Docker Compose与Nginx配合实现自动续期。另一种更简洁的方案是使用Caddy作为反向代理,它内置了自动HTTPS功能,无需手动申请证书,配置文件只需几行即可完成所有工作。无论选择哪种方案,都要定期检查证书有效期,避免证书过期导致服务不可用。
数据持久化与日常运维管理
容器化部署最大的风险之一是数据丢失。Excalidraw本身是前端应用,用户绘制的图表数据默认存储在浏览器IndexedDB中,这部分数据不依赖服务器。但如果启用了服务端存储或协作功能,则需要确保Redis中的会话数据持久化。在Compose文件中已经使用命名卷redis-data挂载到Redis容器的/data目录,即使容器删除重建,数据依然保留。
定期备份是保障数据安全的重要手段。可以编写一个简单的备份脚本,通过cron定时执行:
#!/bin/bash # Excalidraw Redis数据备份脚本 BACKUP_DIR="/data/backups/excalidraw" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR # 触发Redis后台持久化 docker exec excalidraw-redis redis-cli BGSAVE # 等待持久化完成 sleep 5 # 复制RDB文件到备份目录 docker cp excalidraw-redis:/data/dump.rdb $BACKUP_DIR/redis_$DATE.rdb # 压缩备份文件 gzip $BACKUP_DIR/redis_$DATE.rdb # 清理30天前的旧备份 find $BACKUP_DIR -name "redis_*.rdb.gz" -mtime +30 -delete echo "备份完成: redis_$DATE.rdb.gz"
日常运维还需要关注容器健康状态。Dockerfile中已经添加了HEALTHCHECK指令,Docker会每30秒检查一次Nginx是否正常响应。配合restart: unless-stopped策略,当容器异常退出时会自动重启。建议同时配置监控告警系统,使用Prometheus采集容器CPU、内存、网络等指标,Grafana展示监控面板,当服务异常或资源使用率过高时及时通知运维人员。
镜像升级是日常运维的另一个重要环节。更新Excalidraw版本时,只需拉取最新源码重新构建镜像,然后执行docker compose up -d即可滚动更新。由于用户数据存储在浏览器端和Redis卷中,升级过程不会丢失任何数据。建议在非高峰时段执行升级操作,并提前在测试环境验证新版本的兼容性。升级后检查Nginx日志和容器日志,确认服务正常运行,WebSocket连接正常建立,协作功能测试通过后再开放给团队使用。
日志管理方面,Docker默认使用json-file日志驱动,长期运行会导致日志文件不断增长。可以在/etc/docker/daemon.json中配置日志轮转策略,限制单个日志文件大小和保留数量。也可以将日志驱动改为fluentd或loki,将容器日志统一收集到集中式日志平台,方便检索和分析。对于Excalidraw这种以静态文件为主的应用,日志量通常不大,但协作后端的Socket.IO日志可能会在高峰期产生较多输出,需要重点关注。
DockerExcalidraw容器化部署修改时间:2026-08-26 02:25:16