Many selfhosters are aware of Excalidraw.

A virtual whiteboard for sketching hand-drawn like diagrams.
GitHub - excalidraw/excalidraw: Virtual whiteboard for sketching hand-drawn like diagrams
Virtual whiteboard for sketching hand-drawn like diagrams - excalidraw/excalidraw

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:

How can both Excalidraw and Excalidraw-room work together? · Issue #369 · excalidraw/excalidraw-room
What set up do I need in my docker-compose file, to make both containers work together? A compose file from a different issue on this repo doesn’t seem to make sense and there doesn’t seem to be an…
Question - How to try out collaboration using this repo and excalidraw repo? · Issue #364 · excalidraw/excalidraw-room
I looked at the codebase, two repo uses two different server, excalidraw talks to 3000 and this opens socket at 3002. How to make the excalidraw from 3000 to talk to 3002?
Self Hosted Collab Excalidraw with containers · Issue #366 · excalidraw/excalidraw-room
Hello, I want to setup a self hosted environnement using the 2 containers: excalidraw and excalidraw-room There is no documentation on how to connect / link the client to the server.. I tried to se…
I want to have self-host server. · Issue #5188 · excalidraw/excalidraw
Hello everybody. Thank you for your great project. When can I have a self-host server project where collaboration is enabled?

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.com

docker-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.

🧠
Make sure to replace the example domains *.example.com with your own (sub)domains. Also, do not forget to define VITE_APP_WS_SERVER_URL.
🚨
Please note that TLS/HTTPS is required. Use a reverse proxy!

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: true

docker-compose.yml

Supported Collaboration in selfhosted Excalidraw

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!

LRVT
Hey!If you enjoy my online content, please feel free to buy me a coffee or slice of pizza. Many thanks!