Many selfhosters are aware of Excalidraw.
A virtual whiteboard for sketching hand-drawn like diagrams.
However, there is a big difference between a selfhosted instance and the official platform provided by Excalidraw. Namely:
Support for collaboration
Is Collaboration Even Possible?
Kinda?
Although there is a Github repository called excalidraw-room and also a Dockerhub image, many people seem having issues getting collaboration to work with their self-hosted excalidraw instance.
Here some common issues:
Why Collaboration Fails
The biggest problem seems to be that the official Excalidraw docker container uses a hardcoded collaboration URL https://oss-collab.excalidraw.com , defined in env.production.
One cannot overwrite it by just defining a different URL using the environment variable VITE_APP_WS_SERVER_URL, as the JavaScript assets are already fixed to the aforementioned hardcoded URL during build-time.
As people have outlined in Github issues, one would have to clone the official Excalidraw repository, adjust the environment variable and then manually build the container. Wow.
What a waste of time and increase in complexity for patch management.
Let's Fix Collaboration
Instead of manually building an Excalidraw image with our own VITE_APP_WS_SERVER_URL, we will simply overwrite the entrypoint of Excalidraw.
This allows adjusting the hardcoded collaboration URL https://oss-collab.excalidraw.com programmatically in all JavaScript assets with our own defined URL, taken from the environment variable VITE_APP_WS_SERVER_URL during runtime.
Here an example:
services:
excalidraw:
image: excalidraw/excalidraw:latest
container_name: excalidraw
expose:
- 80
restart: unless-stopped
# let's fix collaboration by overwriting the hardcoded collab-url
entrypoint: /bin/sh
command:
- -c
- |
echo "Replacing WebSocket URL with: $$VITE_APP_WS_SERVER_URL"
find /usr/share/nginx/html/assets -type f -name "*.js" -exec sed -i 's|https://oss-collab\.excalidraw\.com|'$$VITE_APP_WS_SERVER_URL'|g' {} +
echo "Starting nginx..."
nginx -g 'daemon off;'
stdin_open: true
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:80/ >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
environment:
- NODE_ENV=production
- VITE_APP_WS_SERVER_URL=https://draw2gether.example.comdocker-compose.yml with fixed entrypoint for excalidraw
And that's basically it. Collaboration will be supported from now on.
No custom images. Just a fixed entrypoint 😉
Full Compose Example using Traefik
Here is a working, hardened Docker Compose example using Traefik as reverse proxy with labels.
Spawning both, the Excalidraw frontend as well as the Excalidraw room service for collaboration.
*.example.com with your own (sub)domains. Also, do not forget to define VITE_APP_WS_SERVER_URL.Otherwise, you will see
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'generateKey') in developer console for file encryption.ts:17 and collaboration fails.services:
excalidraw:
image: excalidraw/excalidraw:latest
container_name: excalidraw
expose:
- 80 # web-ui for drawing
restart: unless-stopped
entrypoint: /bin/sh
command:
- -c
- |
echo "Replacing WebSocket URL with: $$VITE_APP_WS_SERVER_URL"
find /usr/share/nginx/html/assets -type f -name "*.js" -exec sed -i 's|https://oss-collab\.excalidraw\.com|'$$VITE_APP_WS_SERVER_URL'|g' {} +
echo "Starting nginx..."
nginx -g 'daemon off;'
stdin_open: true
environment:
- NODE_ENV=production
- VITE_APP_WS_SERVER_URL=https://draw2gether.example.com
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:80/ >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
- /var/cache/nginx/client_temp:rw,noexec,nosuid,size=64m
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.excalidraw.rule=Host(`draw.example.com`)
- traefik.http.services.excalidraw.loadbalancer.server.port=80
- traefik.docker.network=proxy
excalidraw-room:
image: excalidraw/excalidraw-room:latest
container_name: excalidraw-room
expose:
- 80 # web-socket for collaboration
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:80/ >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.excalidraw-collab.rule=Host(`draw2gether.example.com`)
- traefik.http.services.excalidraw-collab.loadbalancer.server.port=80
- traefik.docker.network=proxy
networks:
proxy:
external: truedocker-compose.yml

Just browse your Excalidraw instance and trigger the Live collaboration from the left-side hamburger menu. Share the link and if another person opens it, a new session will occur. Visible at the top right (people icon) as well as via an interactive mouse pointer if the other person is active.
May clear cache if it does not work instantly. Browsers love caching JavaScript 🫠
Cheers!


Discussion