A self-hosted dashboard and organizer for Excalidraw with live collaboration features.
(Optional) Multi User Authentication, OIDC Support
Export/import your drawings for backup
See release notes for a specific release.
ExcaliDash includes an in-app update notifier that checks GitHub Releases. If your deployment must not make outbound network calls, disable it on the backend:
UPDATE_CHECK_OUTBOUND=falseIf you deployed using docker-compose.prod.yml (Docker Hub images), upgrade by pulling the latest images and recreating containers:
docker compose -f docker-compose.prod.yml pull && \
docker compose -f docker-compose.prod.yml up -dIf you prefer a clean stop/start (more downtime, but simpler), you can do:
docker compose -f docker-compose.prod.yml down && \
docker compose -f docker-compose.prod.yml pull && \
docker compose -f docker-compose.prod.yml up -dNotes:
- Don’t add
-vtodownunless you intend to delete the persistent backend volume (your SQLite DB + secrets). - Only add
--remove-orphansif you previously ran a different Compose file for the same project name and need to remove old/renamed services.
Caution
This is a BETA deployment and production-readiness depends on deployment controls: use TLS, trusted reverse proxy, fixed secrets, backups, and endpoint rate limits.
Caution
ExcaliDash is in BETA. Please backup your data regularly.
Prereqs: Docker + Docker Compose v2.
Docker Hub (Recommended)
# Download docker-compose.prod.yml
curl -OL https://raw.githubusercontent.com/ZimengXiong/ExcaliDash/main/docker-compose.prod.yml
# Pull images
docker compose -f docker-compose.prod.yml pull
# Run container
docker compose -f docker-compose.prod.yml up -d
# Access the frontend at localhost:6767For single-container deployments, JWT_SECRET can be omitted and will be auto-generated and persisted in the backend volume on first start. For portability and most production deployments, set a fixed JWT_SECRET explicitly.
By default, the provided Compose files set TRUST_PROXY=false for safer setup. Only set TRUST_PROXY to a positive hop count (for example, 1) when requests always pass through a trusted reverse proxy that correctly sets forwarded headers.
Docker Build
# Clone the repository (recommended)
git clone git@github.com:ZimengXiong/ExcaliDash.git
# or, clone with HTTPS
# git clone https://github.com/ZimengXiong/ExcaliDash.git
docker compose build
docker compose up -d
# Access the frontend at localhost:6767Reverse Proxy / Traefik
When running ExcaliDash behind Traefik, Nginx, or another reverse proxy, configure both containers so that API + WebSocket calls resolve correctly:
| Variable | Purpose |
|---|---|
FRONTEND_URL |
Backend allowed origin(s). Must match the public URL users access (for example https://excalidash.example.com). Supports comma-separated values for multiple addresses. |
TRUST_PROXY |
Set to 1 when traffic passes through one trusted reverse-proxy hop (for example frontend nginx -> backend) and headers are sanitized. |
BACKEND_URL |
Frontend container-to-backend target used by Nginx. Override when backend host differs from default service DNS/host. |
# docker-compose.yml example
backend:
environment:
# Single URL
- FRONTEND_URL=https://excalidash.example.com
# Trust exactly one reverse-proxy hop
- TRUST_PROXY=1
# Or multiple URLs (comma-separated) for local + network access
# - FRONTEND_URL=http://localhost:6767,http://192.168.1.100:6767,http://nas.local:6767
frontend:
environment:
# For standard Docker Compose (default)
# - BACKEND_URL=backend:8000
# For Kubernetes, use the service DNS name:
- BACKEND_URL=excalidash-backend.default.svc.cluster.local:8000Scaling / HA (Current Limitations)
ExcaliDash currently supports running one backend instance.
Why:
| Area | Limitation |
|---|---|
| Database | The backend uses a local SQLite file database by default (DATABASE_URL=file:/.../dev.db). Running multiple backend replicas either creates split-brain state (separate DB files/volumes) or requires sharing a single SQLite file across hosts, which is not a reliable deployment pattern. |
| Collaboration | Real-time presence state is tracked in-memory in the backend process, so multiple replicas will fragment presence/collaboration unless a shared Socket.IO adapter is added. |
Recommended deployment pattern:
| Component | Guidance |
|---|---|
| Backend | 1 replica, persistent volume, regular backups. |
| Frontend | 1 replica is simplest; scaling is generally fine since it is stateless. |
Auth, Onboarding, and First Admin Setup
ExcaliDash supports local login and OIDC, and includes a one-time first-admin bootstrap key to protect initial setup/migration flows.
Auth modes:
AUTH_MODE |
Behavior |
|---|---|
local (default) |
Native email/password login only. |
hybrid |
Native login plus OIDC login. |
oidc_enforced |
OIDC-only login (/auth/register and /auth/login disabled). |
If you upgrade and see an onboarding/setup flow, follow the UI. For emergency-only operator access, you can temporarily bypass the onboarding gate:
DISABLE_ONBOARDING_GATE=true docker compose -f docker-compose.prod.yml up -dOne-time first-admin bootstrap setup code (local auth only):
| What | Notes |
|---|---|
| When required | Auth enabled and no active users (fresh install or certain migrations). |
| Where to find it | Backend logs: [BOOTSTRAP SETUP] One-time admin setup code .... |
| Behavior | Single-use; if you enter an invalid/expired code, check logs for the refreshed code. |
Find the current code in logs:
docker compose -f docker-compose.prod.yml logs backend --tail=200 | grep "BOOTSTRAP SETUP"OIDC configuration (for hybrid / oidc_enforced) requires these backend env vars:
backend:
environment:
- AUTH_MODE=oidc_enforced
- OIDC_PROVIDER_NAME=Authentik
- OIDC_ISSUER_URL=https://auth.example.com/application/o/excalidash/
- OIDC_CLIENT_ID=your-client-id
# Optional for public clients; required for confidential clients
# - OIDC_CLIENT_SECRET=your-client-secret
- OIDC_REDIRECT_URI=https://excalidash.example.com/api/auth/oidc/callback
- OIDC_SCOPES=openid profile emailNotes:
| Topic | Notes |
|---|---|
OIDC-only (oidc_enforced) |
You typically do not use local bootstrap admin registration; first admin can be created through your IdP depending on config. |
| Reverse proxy | Set FRONTEND_URL and TRUST_PROXY correctly or auth + websockets may fail. |
Local OIDC Test Stack (Docker + Keycloak)
This repo includes a Keycloak container + realm seed for local OIDC testing:
- Compose file:
docker-compose.oidc.yml - Realm import:
oidc/keycloak/realm-excalidash.json
The realm seed intentionally contains no users and no passwords. You create a realm user and set a password via the Keycloak admin UI.
Start Keycloak:
# From repo root
# Choose a strong password; do not commit it.
export KEYCLOAK_ADMIN_PASSWORD='...'
docker compose -f docker-compose.oidc.yml up -dOpen Keycloak admin UI (realm/user setup):
http://localhost:8080/admin- Switch realm to
excalidash - Create a user and set a password in
Credentials
Configure ExcaliDash backend for hybrid OIDC:
cd backend
cp .env.oidc.example .env
# Ensure OIDC_REDIRECT_URI matches where your frontend is running:
# - http://localhost:6767/api/auth/oidc/callback (repo frontend dev default)
# - https://excalidash.example.com/api/auth/oidc/callback (production)Stop/clean up:
docker compose -f docker-compose.oidc.yml downConfiguration (Backend Environment Variables)
Base values are documented in backend/.env.example. Common ones to care about:
| Variable | Default / Example | Description |
|---|---|---|
DATABASE_URL |
file:/app/prisma/dev.db |
SQLite file or external DB URL. |
FRONTEND_URL |
http://localhost:6767 |
Allowed frontend origin(s), comma-separated for multiple entries. |
TRUST_PROXY |
false |
false, true, or hop count (for example 1). |
JWT_SECRET |
change-this-secret... |
Recommended in production so sessions remain stable across restarts and migrations. |
CSRF_SECRET |
change-this-secret |
Recommended in production so CSRF validation remains stable across restarts. |
AUTH_MODE |
local |
local, hybrid, oidc_enforced. |
For contributor workflow, make dev starts the app in local single-user mode so you can reproduce editor bugs without going through login/onboarding. Use make dev-auth if you need to test local auth or OIDC flows from your backend/.env.
Clone the Repository
# Clone the repository (recommended)
git clone git@github.com:ZimengXiong/ExcaliDash.git
# or, clone with HTTPS
# git clone https://github.com/ZimengXiong/ExcaliDash.gitFrontend
cd ExcaliDash/frontend
npm install
# Copy environment file and customize if needed
cp .env.example .env
npm run devBackend
cd ExcaliDash/backend
npm install
# Copy environment file and customize if needed
cp .env.example .env
# Generate Prisma client and setup database
npx prisma generate
npx prisma db push
npm run devSimulate Auth Onboarding (Development)
To simulate first-run authentication choice flows in local development:
cd ExcaliDash/backend
# Preview what would change (no data modifications)
npm run dev:simulate-auth-onboarding:dry-run
# Simulate "fresh install" onboarding state
# (wipes drawings/collections/libraries and removes non-bootstrap users)
npm run dev:simulate-auth-onboarding:fresh
# Simulate "migration" onboarding state (ensures legacy data exists)
npm run dev:simulate-auth-onboarding:migrationAfter running a simulation while the backend is already running, wait about 5 seconds (auth mode cache TTL) or restart the backend before refreshing the UI.
Setup and Operational Scripts
In backend/package.json there are helper scripts for maintenance:
| Script | Purpose |
|---|---|
admin:recover |
Emergency admin credential recovery/reset. |
Admin recovery example:
cd backend
npm run admin:recover -- --identifier admin@example.com --generate --activate --must-resetCommon flags:
| Flag | Description |
|---|---|
--password "<new-password>" |
Set explicit new password. |
--generate |
Generate a secure random password. |
--activate |
Activate the admin account immediately. |
--promote |
Promote user to admin role. |
--must-reset |
Force password reset on first login. |
--disable-login-rate-limit |
Temporarily disable login throttling for this operation. |











