Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Whiteboard

Self-hosted Excalidraw with working live collaboration and share links, deployed on Coolify at whiteboard.ciya.so.

Unlike the stock excalidraw/excalidraw image, no drawing data leaves this server. Sharing and collaboration are backed by our own room and storage services rather than Excalidraw's public infrastructure.

How it fits together

Everything is served from one origin. Coolify assigns a domain to gateway only; the other four services stay on the internal Docker network.

whiteboard.ciya.so
  └── gateway (nginx)
        ├── /            → app      the Excalidraw client
        ├── /socket.io/  → room     live collaboration (websocket)
        └── /api/v2/     → storage  share links, room scenes, images
                                      └── redis (persistence)
Service Image Role
gateway built from nginx/ Single public entrypoint, path routing
app alswl/excalidraw:v0.18.1-fork-b2 Excalidraw client
room excalidraw/excalidraw-room:sha-03ff435 Socket.io collaboration server
storage alswl/excalidraw-storage-backend:v2023.11.11 Scene, room and image storage
redis redis:7-alpine Durable store behind storage

Why a gateway instead of three domains

Two constraints push toward this shape rather than giving each service its own subdomain or path-based domain in the Coolify UI:

  • Coolify adds a Traefik stripprefix middleware to any domain whose path is not /. Routing /socket.io that way would strip the prefix and break the handshake.
  • Keeping one origin means no CORS configuration anywhere, and the entire routing table lives in nginx/excalidraw.conf in git rather than in UI toggles that are easy to lose.

The gateway is built from a one-line nginx/Dockerfile rather than using the stock image with the config bind-mounted. Coolify rewrites relative bind sources to a host path under /data/coolify/applications/<uuid>/ and pre-creates them as directories, so mounting a single file fails with not a directory. Baking the config into the image keeps it in its own file and touches no host path.

Why a forked client image

The official image bakes its Vite variables in at build time — .env.production hardcodes VITE_APP_WS_SERVER_URL to oss-collab.excalidraw.com and VITE_APP_BACKEND_V2_* to json.excalidraw.com, and no runtime environment variable can change them. The alswl fork ships a launcher.py entrypoint that rewrites the built assets on container start, so the same published image can be pointed at our own services with plain environment variables and no build step.

Setting VITE_APP_FIREBASE_CONFIG={} together with VITE_APP_STORAGE_BACKEND=http is what detaches the client from Excalidraw's Firebase, which normally holds live collaboration scenes.

Deploying on Coolify

  1. Add a DNS A record for whiteboard.ciya.so pointing at the Coolify server.
  2. In Coolify create a new resource, type Docker Compose, sourced from this repository.
  3. Set the environment variable APP_URL=https://whiteboard.ciya.so. Coolify marks it required, and deployment fails fast if it is missing.
  4. On the gateway service, set the domain to https://whiteboard.ciya.so. Leave app, room, storage and redis with no domain so they are not exposed.
  5. Deploy. All five containers should report healthy.

APP_URL has to match the address people actually use. It is compiled into the client as the origin for both the websocket and the storage API, so a mismatch shows up as collaboration silently failing to connect.

Restricting access

The whiteboard is open to anyone who knows the URL. Coolify supports HTTP Basic Auth per resource under the service's settings, which is the quickest lock if you want one.

Local testing

docker-compose.local.yml publishes the gateway on port 8080. It is not named docker-compose.override.yml, so Coolify never merges it.

APP_URL=http://localhost:8080 docker compose \
  -f docker-compose.yml -f docker-compose.local.yml up -d

Open http://localhost:8080. Collaboration works here without a certificate because localhost is treated as a secure origin.

APP_URL=http://localhost:8080 docker compose \
  -f docker-compose.yml -f docker-compose.local.yml down

Add -v to that last command to also discard the Redis volume.

Notes and caveats

These images are amd64-only. alswl/excalidraw, alswl/excalidraw-storage-backend and excalidraw/excalidraw-room all publish a single linux/amd64 manifest. On an ARM host they will only run under emulation, if at all. To build natively instead, replace the image: line with a git build context, for example:

app:
  build:
    context: https://github.com/alswl/excalidraw.git#feat/env-dynamic-in-docker-container-2025-12

Expect a slow, memory-hungry first build, and the same treatment for room and storage.

Redis persistence is required. The storage backend defaults to STORAGE_URI="", an in-memory store that drops every share link on restart. The redis://redis:6379 setting plus the redis-data volume is what makes links durable.

Healthchecks use 127.0.0.1, not localhost. These containers listen on IPv4 only, and inside them localhost resolves to ::1 first and is refused. The app service's healthcheck is overridden here for exactly this reason, since the one baked into the image targets localhost and never passes.

The library browser still calls out. VITE_APP_LIBRARY_URL and VITE_APP_LIBRARY_BACKEND keep their defaults and point at libraries.excalidraw.com. That is the public shape catalogue, reached only when you open the library panel, and it carries none of your drawings. Point them elsewhere or set them empty in docker-compose.yml if you want the instance fully sealed.

excalidraw-room was last published in December 2023. It is stable rather than abandoned — the socket protocol has not changed and it is what every self-hosted Excalidraw setup uses — but it is not actively developed.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages