# Excalidraw whiteboard

Source: https://docs.quake.ai/resources/iac-templates/excalidraw-whiteboard
Markdown: https://docs.quake.ai/resources/iac-templates/excalidraw-whiteboard.md

---

# Excalidraw whiteboard

This pattern composes Compute and Network into a self-hosted collaborative whiteboard for a team, on infrastructure you control.

## What this template does

Provisions a single instance running [Excalidraw](https://excalidraw.com), an open-source collaborative whiteboard (a self-hosted alternative to Figma or Miro for lightweight diagramming). Your team sketches diagrams together in real time on infrastructure you own:

- Compute instance that runs the Excalidraw frontend and the `excalidraw-room` collaboration server in Docker, two lightweight, stateless containers with no bundled datastore (2 vCPU and 2 GiB RAM, the lightest ops-tools footprint in this library)
- Private network, subnet, router, port, and security group; a floating IP for public access
- No block volume: neither container persists drawing content server-side, so the boot disk is sufficient
- cloud-init installs Docker Engine and writes the compose file, but does not start either container until you finish configuration

There is no credential to generate for this template: nothing here needs one.

## The published image bakes in its own collaboration URL

The `excalidraw/excalidraw` frontend is a Vite single-page app. `VITE_APP_WS_SERVER_URL` is inlined into the built JavaScript at image-build time, not read at container runtime, so the published image always points at Excalidraw's own public collaboration server (`oss-collab.excalidraw.com`) no matter what environment variable you set on the running container. Three independently authored self-hosting guides document the same workaround, cross-checked because this is a known rough edge in the official image: override the frontend container's entrypoint to `sed`-patch the hardcoded URL out of the built JavaScript assets at container start, using the real value of `VITE_APP_WS_SERVER_URL`, before starting Nginx. This template ships that patch as the container's entrypoint rather than a hidden workaround you would have to discover yourself.

## Parameters

| Parameter | Description | Default |
| --- | --- | --- |
| `key_name` | SSH keypair name (must already exist) | No default |
| `flavor_name` | Instance size (both containers are lightweight and stateless, 2 vCPU / 2 GiB) | `s1a.small` |
| `image_name` | Operating system image | `Ubuntu-24.04` |
| `app_name` | Display name prefix for resources | `excalidraw` |
| `external_network` | External network for floating IP allocation | `PublicStatic` |
| `private_cidr` | CIDR for the private subnet | `10.58.0.0/24` |
| `app_allowed_cidr` | CIDR allowed to reach the frontend (8080) and collaboration server (8081) | `10.58.0.0/24` |

## Finish setup after apply

The frontend's collaboration patch depends on knowing the collaboration server's real public domain, and Excalidraw needs HTTPS (its end-to-end encryption uses `window.crypto.subtle`, which browsers only expose on a secure context), so cloud-init holds both containers until you finish configuration:

1. Point two domains at the floating IP: one for the frontend (for example `draw.example.com`), one for the collaboration server (for example `collab.example.com`). These need to be two separate hostnames, each with its own HTTPS termination, because the frontend and the room server are reached at two different addresses.
2. Put a reverse proxy in front of each: the frontend domain to port 8080, the collaboration domain to port 8081, with WebSocket upgrade enabled on the collaboration domain's proxy block.
3. Edit `/opt/excalidraw/.env`: set `VITE_APP_WS_SERVER_URL` to your collaboration server's public HTTPS address.
4. Start both containers:

```bash
cd /opt/excalidraw
sudo docker compose up -d
```

5. Open the frontend's HTTPS address and start a live collaboration session to confirm the patch applied.

## Access and security

Excalidraw's frontend listens on port 8080 and the collaboration server on port 8081, both over plain HTTP. The security group restricts both ports to `app_allowed_cidr`, which defaults to the private network only. The collaboration server has no built-in authentication: anyone with a live share link can join that room, which suits a small trusted team rather than a public-facing deployment.

## When to use this pattern

Run lightweight diagramming and whiteboarding for a team on a host you operate. There is nothing to back up server-side beyond the reverse-proxy configuration: drawing content lives in each browser's local storage, and the room server relays end-to-end-encrypted data without persisting it.

## Estimated cost

<PricingCompanion
  components={[
    { kind: "template", slug: "excalidraw-whiteboard", required: true },
  ]}
/>

## Template source

<TemplateSource slug="excalidraw-whiteboard" />

<TemplateResourceMap template="excalidraw-whiteboard" format="opentofu" />

## Customize this pattern

- [Customize a template's image and flavor](/docs/automation/how-to/customize-template-image-flavor)
- [Parameterize a template with a tfvars file](/docs/automation/how-to/parameterize-template-tfvars)

## See also

- [Mattermost team chat](/resources/iac-templates/mattermost-team-chat)
- [Nextcloud files and collaboration](/resources/iac-templates/nextcloud-files)
- [Infisical secrets management](/resources/iac-templates/infisical-secrets)
