# Deploy Excalidraw with the excalidraw-whiteboard template

Source: https://docs.quake.ai/resources/deployments/deploy-excalidraw-whiteboard-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-excalidraw-whiteboard-template.md

---

# Deploy Excalidraw with the excalidraw-whiteboard template

Stand up [Excalidraw](https://excalidraw.com), an open-source collaborative whiteboard, on a single Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `excalidraw-whiteboard`. You apply the template, point two subdomains at the host and serve both over HTTPS through Caddy, set the collaboration server's domain and start the containers, open the app, start a live collaboration session, and confirm real-time sync from a second browser session.

Excalidraw keeps your team's diagrams on infrastructure you own. You run it yourself; this is a self-hosted tool you operate, not a managed multi-tenant service.



Excalidraw's frontend and its collaboration server are reached at two different hostnames: one for the whiteboard app, one for the WebSocket relay. Each needs its own DNS record and its own HTTPS termination through the reverse proxy, and the collaboration hostname's proxy block needs WebSocket upgrade enabled. Excalidraw also uses `window.crypto.subtle` for its end-to-end encryption, which browsers only expose on a secure context, so plain HTTP will not work for either hostname.



<Figure size="md" caption="What you'll build: an Excalidraw host on a single instance with two hostnames, each served over HTTPS through a Caddy reverse proxy">

```d2
direction: right

user1: Team member {shape: person}
user2: Teammate {shape: person}
fip: Floating IP
instance: Ubuntu instance {
  caddy: Caddy\nreverse proxy
  frontend: Excalidraw\nfrontend
  room: excalidraw-room\ncollaboration server
  caddy -> frontend: draw.example.com -> 8080
  caddy -> room: collab.example.com -> 8081 (WebSocket)
  frontend -> room: encrypted drawing sync
}

user1 -> fip: HTTPS
user2 -> fip: HTTPS
fip -> instance.caddy
```

</Figure>

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

## Prerequisites

You need:

- OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
- Your OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An SSH keypair that already exists in your project. Record its name for the `key_name` variable.
- A copy of the `excalidraw-whiteboard` template directory from [the template reference page](/resources/iac-templates/excalidraw-whiteboard).
- Two subdomains you can point at the instance.

## Step 1: Apply the template

Copy the template's example variables file and set `key_name`:

```bash
cp terraform.tfvars.example terraform.tfvars
```

```hcl
key_name = "YOUR_KEY_NAME"
```

Initialize, preview, and apply:

```bash
tofu init
tofu plan
tofu apply
```

OpenTofu provisions a private network, a router, a security group, an instance, and a floating IP. On first boot, cloud-init installs Docker Engine and writes `/opt/excalidraw/docker-compose.yml` and `/opt/excalidraw/.env`, but does not start either container: the frontend's collaboration-URL patch depends on knowing the real public domain first.

Read the outputs and record `floating_ip`:

```bash
tofu output
```

## Step 2: Point two domains at the host and serve HTTPS with Caddy

1. Create two DNS **A records**, for example `draw.example.com` and `collab.example.com`, both pointing at `YOUR_FLOATING_IP`. Follow [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai). Wait until both resolve:

```bash
dig +short draw.example.com
dig +short collab.example.com
```

2. SSH to the instance and create `/opt/excalidraw/Caddyfile`:

```text
draw.example.com {
  reverse_proxy 127.0.0.1:8080
}

collab.example.com {
  reverse_proxy 127.0.0.1:8081
}
```

Caddy upgrades WebSocket connections automatically inside `reverse_proxy`, so the collaboration block needs no extra configuration.

3. Add Caddy to `/opt/excalidraw/docker-compose.yml`:

```yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    volumes:
      - /opt/excalidraw/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  caddy_data:
```

For background on certificates, see [How to issue and auto-renew a TLS certificate with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate).

## Step 3: Set the collaboration domain and start Excalidraw

Edit `/opt/excalidraw/.env` and set:

```text
VITE_APP_WS_SERVER_URL=https://collab.example.com
```

Start the full stack:

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

Check the frontend container's logs for the patch confirmation:

```bash
docker compose logs excalidraw | grep "Patching hardcoded collab URL"
```

## Step 4: Start a live collaboration session

1. Open `https://draw.example.com`.
2. Open the hamburger menu in the top left and select **Live collaboration**.
3. Select **Start session** and copy the generated share link.

## Step 5: Confirm real-time sync from a second browser session

1. Open the share link in a second browser or an incognito window.
2. Draw a shape in one window and confirm it appears in the other within a second or two.
3. Open the browser's developer tools, filter the network tab to `WS`, and confirm the open WebSocket connects to `collab.example.com`, not `oss-collab.excalidraw.com`. That confirms the collaboration-URL patch applied correctly.

## What you built

- **Applied the `excalidraw-whiteboard` template** to provision a network, security group, instance, and floating IP, with both containers held until configuration finished
- **Served two hostnames over HTTPS** by pointing both domains at the floating IP and routing them through a Caddy reverse proxy, with WebSocket upgrade on the collaboration hostname
- **Set the collaboration domain** and started both containers
- **Started a live collaboration session** and confirmed real-time sync between two browser sessions

## Scope of this deployment

This template runs a single-VM Excalidraw host, not a managed multi-tenant whiteboard service. The instance is CPU-only and runs in one region, and it bundles no database because there is nothing to persist server-side: drawing content lives in each browser's local storage, and the collaboration server relays end-to-end-encrypted data without persisting it. You operate the instance, Docker, and both containers yourself. The collaboration server has no built-in authentication, so anyone with a live share link can join a room; this suits a small trusted team rather than a public-facing deployment.

## Next steps

- [Excalidraw whiteboard template](/resources/iac-templates/excalidraw-whiteboard): the template reference, parameters, and resource map
- [Nextcloud files and collaboration template](/resources/iac-templates/nextcloud-files): another ops-tools template with a similar domain-first setup pattern
- [Security hardening checklist](/docs/security/hardening-checklist): tighten SSH access and exposure before you connect production traffic

## Clean up

When you no longer need the deployment, destroy everything the template created:

```bash
tofu destroy
```

Then remove the two DNS A records you created in step 2.
