Self-hosted service
alswl/excalidraw-collaboration avatar
alswl/excalidraw-collaboration

excalidraw-collaboration: self-hosting Excalidraw with live rooms

excalidraw with collaboration feature, self-hosting, and only one-click deploy

632 stars106 forksUnknownMIT

At a glance

What is it?
excalidraw-collaboration packages a self-hosted Excalidraw with a Socket.IO room server, HTTP scene storage and two Docker Compose deployments, one local and one behind Nginx with TLS. It suits teams that want a shared drawing board on their own infrastructure, and it is built on separate upstream images that you have to pin and keep in step.
Who is it for?
excalidraw-collaboration earns its place for a team that wants collaborative drawing on infrastructure it controls, with a local example that starts in three commands and a production example that puts everything behind one domain with TLS. It is the wrong choice for a group that needs object storage, single sign-on or a Kubernetes deployment, since all three are still unchecked items on the project's own roadmap.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 76 days ago.
What is it written in?
GitHub does not report a main language for this repository.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the project is: four moving parts around Excalidraw

The repository describes itself as self-hosted Excalidraw with live collaboration, scene storage, and a small set of Docker Compose deployment examples. The four included pieces are listed in the README:

An Excalidraw frontend configured through runtime environment variables. HTTP storage for scenes and files. A Socket.IO room service for real-time collaboration, powered by a separate project called excalidraw-room-go. And a local Compose example plus an HTTPS and Nginx production example.

That last point is the structural fact worth noticing. This repository is not a fork of Excalidraw with collaboration added; it is an assembly of components, three of which live in sibling repositories under the same author. The related projects list names excalidraw-room-go, described as the Go implementation of the Excalidraw Socket.IO room server, excalidraw-storage-backend, the storage backend used by this stack, and a self-hosting-focused Excalidraw fork used for the frontend image. There is also excalidraw.alswl.com, a static Excalidraw site with Chinese font support, which is a separate project rather than part of this stack.

The consequence for an adopter is that you are choosing a composition of upstream images rather than a single application, and each component moves on its own schedule. The README's instruction to keep the room image pinned to a released version is the acknowledgement of that.

The licences and the provenance are simple: MIT, and a project by the author alswl. The releases are named after the fork they track, v0.18.1-fork-b3 published on 2026-08-08, v0.18.1-fork-b2 on 2026-05-14 and v0.18.1-fork-b1 on 2026-05-13, with the last push to the repository on 2026-08-08 and the repository not archived.

The wiring: browser, frontend, storage, room server and Nginx

The README includes a diagram of the service topology, and it is worth reading because the traffic paths differ and that difference drives the deployment decisions.

The browser talks to three things. It goes to the Excalidraw frontend, it goes to the storage backend for scenes and files, and it holds a bidirectional Socket.IO connection to the room server. Nginx sits in front of all three and forwards to each.

So the frontend is not a gateway. The browser reaches storage directly, and the browser reaches the room server directly, which means a one-domain deployment has to route three different path prefixes to three different services. The README states exactly how the supplied Nginx virtual host does it: / is forwarded to the frontend, /storage/ to the storage backend, and /socket.io/ to the room service with WebSocket upgrade headers.

The WebSocket upgrade headers are the part that is easy to miss and painful to debug. A Socket.IO endpoint that is proxied without the upgrade headers will accept the initial HTTP request and then never upgrade, so the client appears to connect and never receives events.

One more constraint appears here and again in the FAQ: HTTPS is required for the browser crypto APIs used by collaboration. The browser will only expose those APIs in a secure context, which means a real certificate in production and the localhost exception in development.

Quick start: three commands with Docker Compose

The prerequisite is Docker Compose, and the quick start is a clone and a compose up:

sh
git clone https://github.com/alswl/excalidraw-collaboration.git
cd excalidraw-collaboration
docker-compose -f basic/docker-compose.yaml up -d

After that you open http://127.0.0.1/, select Live Collaboration, and share the room link with another browser session. The local example exposes three services on separate local ports:

| Service | Local address | | Excalidraw | http://127.0.0.1/ | | Storage backend | http://127.0.0.1:8081 | | Room server | http://127.0.0.1:8082 |

That the three ports are distinct is the reason the local example is also a useful development setup rather than only a demo. You can run the frontend from source against the same storage and room services, and the README describes the basic compose file as the local development option for exactly that reason.

Stopping the stack is the matching down command against the same file:

sh
docker-compose -f basic/docker-compose.yaml down

The file paths matter here, since the repository holds more than one deployment: basic/docker-compose.yaml for the local stack and advanced-nginx/compose.yml for the production one, and the README says the exact service wiring is defined in those two files rather than in the prose.

One-domain HTTPS deployment with Nginx

The production example combines advanced-nginx/compose.yml with a virtual host file, draw.example.com.conf. The procedure is to copy the .env-example file to .env, set the host names and database credentials, and then configure the supplied Nginx virtual host.

That is a short list of steps, and each one hides a decision. The host names have to be set for the frontend, the storage endpoint and the room endpoint, and they have to match what the browser will request. The database credentials are for the storage backend, which persists scenes and uploaded files, so this is the point where you decide where your drawings live.

Because the frontend is configured through runtime environment variables rather than baked in at build time, the same frontend image can be pointed at different backends without a rebuild, and the README lists the two variables that matter for a public deployment:

VITE_APP_HTTP_STORAGE_BACKEND_URL and VITE_APP_WS_SERVER_URL.

The prefix tells you when they are read. This project moved from REACT_APP_ prefixed variables to VITE_APP_ ones, and the upgrade guide records that transition explicitly for the v0.15.0 to v0.16.1 step, where the instruction is simply to replace the old prefix with the new one. That is the sort of change that silently breaks a deployment if the environment variables are set in a place the new image does not read.

A third deployment path exists outside this repository: a configurable Traefik based Docker Compose example in the excalidraw-self-hosted project by Someone0nEarth, which the README lists for people who already run Traefik.

The generateKey failure, separate load balancers and the roadmap

The FAQ section contains the failure mode that will hit first, and it is worth knowing before you deploy.

Collaboration can fail with the error TypeError: Cannot read properties of undefined (reading 'generateKey'). The cause is that Excalidraw uses browser cryptography APIs for collaboration, and those APIs are only available in a secure context. The remedy is to open the site over HTTPS, or to use http://localhost for local development.

That single error explains a whole class of problems: a deployment that works on a laptop at localhost and breaks the moment it is behind a plain HTTP proxy, or works for one user and not another depending on how they reached it.

The other FAQ entry covers the case where the frontend, the WebSocket traffic and the storage traffic are exposed through separate load balancers. The instruction is to configure TLS for the WebSocket and storage endpoints, and to point the two frontend variables at the storage endpoint and the WebSocket endpoint respectively. An AWS-oriented deployment discussion lives in issue 22.

The roadmap is a checklist of eight completed items and three that are not. Done: self-hosting, live collaboration, Docker Compose support, dynamic frontend environment configuration, Docker Hub images, an HTTPS demo and documentation, and Railway one-click deployment. Not done: S3 storage support, SSO support and Helm support.

Those three gaps are the honest boundary of the project. There is a live Railway demo and a one-click deploy template for a trial, but storage is the local database, there is no single sign-on, and there is no Helm chart for Kubernetes.

Where this stack is the wrong choice

The first wrong case is a Kubernetes shop. The roadmap has Helm support as an unfinished item, and the deployment paths offered are Compose files and a Railway template. Without a chart, running this in a cluster means writing the manifests yourself, including the Nginx configuration with the WebSocket upgrade headers.

The second is a team whose drawings have to live in object storage. S3 storage support is also on the unfinished list, so the scenes and files go into the storage backend's own database, which is a fine choice for a small team and a problem when you want the same retention and backup policy as everything else.

The third is an organisation that needs SSO. There is no authentication layer in the stack as described, so a deployment behind the company identity provider means putting your own proxy in front of it.

The fourth is a team that wants to keep Excalidraw up to date automatically. The releases here are named after a fork, v0.18.1-fork-b3, and the frontend comes from a separate fork image, so an upgrade means tracking several repositories and rebuilding the wiring. The README's advice to pin the room image to a released version is the right instinct, but it also means you own the upgrade.

The realistic alternative for most of these cases is to use the hosted Excalidraw, where collaboration, storage and identity are somebody else's problem, or to run the Excalidraw fork directly without this stack if you do not need multi-user rooms. The difference in approach is that this project is an infrastructure composition with three coordinated services, and its value is exactly the set of things the hosted service does not give you, which is control over where the data lives and which domain it is served from.

Editorial conclusion

excalidraw-collaboration earns its place for a team that wants collaborative drawing on infrastructure it controls, with a local example that starts in three commands and a production example that puts everything behind one domain with TLS. It is the wrong choice for a group that needs object storage, single sign-on or a Kubernetes deployment, since all three are still unchecked items on the project's own roadmap. Verify first that you can serve HTTPS, because the collaboration features call browser cryptography APIs and fail with a generateKey error otherwise, that the room server image is pinned to a released version as the examples do, and that your frontend environment variables point at the public addresses of your storage and WebSocket endpoints.

Frequently asked questions

What does the excalidraw-collaboration stack include?

An Excalidraw frontend configured through runtime environment variables, HTTP storage for scenes and files, a Socket.IO room service for real-time collaboration powered by excalidraw-room-go, and two deployment examples: a local Compose file and an HTTPS and Nginx production example.

How do I run the stack locally?

With Docker Compose as the prerequisite, clone the repository, change into it and run docker-compose -f basic/docker-compose.yaml up -d. Then open http://127.0.0.1/ and select Live Collaboration. The frontend is served on port 80, the storage backend on 8081 and the room server on 8082.

Why does collaboration fail with a generateKey error?

Excalidraw uses browser cryptography APIs for collaboration, and the error appears when the site is not served over HTTPS. The README's advice is to open the site over HTTPS, or to use http://localhost for local development.

Which environment variables do I set for a public deployment?

VITE_APP_HTTP_STORAGE_BACKEND_URL and VITE_APP_WS_SERVER_URL, set to the public addresses of the storage endpoint and the WebSocket endpoint. Earlier releases used REACT_APP_ prefixed variables, and the upgrade guide for v0.15.0 to v0.16.1 covers that change.

How does the Nginx example route the three services?

The supplied virtual host forwards / to the frontend, /storage/ to the storage backend, and /socket.io/ to the room service with WebSocket upgrade headers. The exact wiring is defined in basic/docker-compose.yaml and advanced-nginx/compose.yml.

What is still missing from this project?

The roadmap lists S3 storage support, SSO support and Helm support as not yet done. The completed items are self-hosting, live collaboration, Docker Compose support, dynamic frontend environment configuration, Docker Hub images, an HTTPS demo and documentation, and Railway one-click deployment.

Official sources

  1. alswl/excalidraw-collaboration on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alswl-excalidraw-collaboration.svg)](https://hysenlabs.com/projects/alswl-excalidraw-collaboration)

Was this useful?