ruinivist 10ee988ce2 feat: add http security headers to caddy config
Adds X-Frame-Options, X-Content-Type-Options, and Referrer-Policy
headers to all responses served by Caddy to mitigate clickjacking and
MIME-type sniffing.
2026-06-05 08:20:05 +00:00
2026-06-01 21:34:05 +00:00
2026-05-24 15:32:36 +00:00
2026-05-31 18:17:00 +00:00
2026-06-01 21:34:05 +00:00
2026-06-01 22:15:13 +00:00
2026-06-01 19:18:39 +00:00
2026-05-31 00:08:51 +00:00
2026-06-01 22:42:02 +00:00
2026-06-01 21:34:05 +00:00
2026-05-31 18:14:41 +00:00
2026-05-24 15:32:36 +00:00

excali-box

Private self-hosted Excalidraw with Bun, Caddy, and SQLite.

This is bare wrapper around the excalidraw packages that adds autosave and disk-persistence. No auth to keep things simple - you either run in a private network or put it behind Caddy auth or similar solutions.

Tl;dr

When building the image yourself

docker run --rm \
  -p 127.0.0.1:3000:80 \
  -e PUBLIC_BASE_URL=http://localhost:3000 \
  -v "$PWD/excali-box-data:/data" \
  excali

When pulling from GHCR ( you would want to change the PUBLIC_BASE_URL )

docker run --rm \
  -p 127.0.0.1:3000:80 \
  -e PUBLIC_BASE_URL=http://localhost:3000 \
  -v "$PWD/excali-box-data:/data" \
  ghcr.io/ruinivist/excalidraw-box:latest

Pull from GHCR and run

Images are published to:

ghcr.io/ruinivist/excalidraw-box:<version>

docker pull ghcr.io/ruinivist/excalidraw-box:latest
docker run --rm -p 127.0.0.1:3000:80 -e PUBLIC_BASE_URL=http://localhost:3000 -v "$PWD/excali-box-data:/data" ghcr.io/ruinivist/excalidraw-box:latest

Run locally

bun install
bun run dev

Open http://localhost:3000.

Test

bun run format
bun run test
bun run typecheck

Build the image

docker build -t excali .
docker run --rm -p 127.0.0.1:3000:80 -e PUBLIC_BASE_URL=http://localhost:3000 -v "$PWD/excali-box-data:/data" excali

This will make the data persistent in the excali-box-data folder in the current directory, with Caddy serving the browser build on localhost:3000. PUBLIC_BASE_URL is required so MCP tools can return drawing URLs with the public browser origin. You would want it to be the same url you use to access the app in the browser - in case you reverse proxy or use a tailscale alias etc.

Optional MCP styles guide resource

The MCP server can optionally expose one extra read-only resource at excali://styles-guide. This file is then discoverable as an MCP resource. Container images already include the repo STYLES_GUIDE.md at /config/styles-guide.md, so the default guide is exposed automatically in Docker/GHCR.

docker run --rm \
  -p 127.0.0.1:3000:80 \
  -e PUBLIC_BASE_URL=http://localhost:3000 \
  -v "$PWD/excali-box-data:/data" \
  -v "$PWD/STYLES_GUIDE.md:/config/styles-guide.md:ro" \
  ghcr.io/ruinivist/excalidraw-box:latest

Bind-mounting a different file to /config/styles-guide.md overrides the bundled default for that container. Local non-container runs are unchanged; if you want a styles guide there, you still need a file at /config/styles-guide.md.