itsez.dev
๐Ÿ“–Tutorial

How to deploy a tldraw app on a VPS

2026-07-30ยท5 min readยทDesign & UI

tldraw is a React SDK for building collaborative infinite-canvas apps, not a server application with a universal Docker image. This guide creates a custom tldraw project, containerises it with nginx and Docker, and serves it behind a reverse proxy on a VPS.

Prerequisites

  • Ubuntu 22.04 or 24.04 VPS with SSH access
  • A domain name (e.g. canvas.example.com) pointing to your server IP
  • Docker and Docker Compose installed on the VPS
  • Node.js 20+ and npm on your local machine or CI
  • Basic knowledge of React and the command line

Important disclaimers

  • tldraw is an SDK, not a server application. You deploy the app you build with it.
  • The self-hosted app serves a single-user editor by default. State is lost on page refresh unless you add persistence.
  • Real-time collaboration requires the @tldraw/sync backend. The recommended deployment target is Cloudflare Workers, not a VPS.
  • Production use of tldraw requires a license key from tldraw.dev. Review the SDK license before publishing.
  • The SDK does not follow semantic versioning. If you use @tldraw/sync, the tldraw and @tldraw/sync package versions must match exactly.

Step 1: Connect and set up the server

ssh root@YOUR_SERVER_IP
apt update && apt upgrade -y
apt install -y curl git

Install Node.js 20.x (LTS):

curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
node --version   # v20.x
npm --version

Step 2: Build a tldraw app

On your local machine or CI, scaffold a new React project with Vite:

npm create vite@latest tldraw-canvas -- --template react
cd tldraw-canvas
npm install tldraw

Open src/App.jsx and replace its content with the minimal tldraw editor:

import { Tldraw } from "tldraw";
import "tldraw/tldraw.css";

export default function App() {
  return (
    <div style={{ position: "fixed", inset: 0 }}>
      <Tldraw />
    </div>
  );
}

Test the app locally:

npm run dev

Open http://localhost:5173 in your browser. You should see a blank canvas with the tldraw toolbar.

Build the static assets for production:

npm run build

The output goes to the dist/ folder.

Step 3: Dockerize the app

Create a Dockerfile in the project root:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Create an nginx.conf file for single-page-app routing:

server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Update the Dockerfile to use this nginx config:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Step 4: Run with Docker Compose

Create a docker-compose.yml file:

services:
  canvas:
    build: .
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:80"

Build and start the container:

docker compose build
docker compose up -d
docker compose ps

The app is now running on 127.0.0.1:3000 and is only reachable from the server itself.

Step 5: Configure the reverse proxy

Install Nginx on the VPS:

apt install -y nginx

Create an Nginx site configuration at /etc/nginx/sites-available/canvas:

server {
    listen 80;
    server_name canvas.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the site:

ln -s /etc/nginx/sites-available/canvas /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Set up HTTPS with Certbot:

apt install -y certbot python3-certbot-nginx
certbot --nginx -d canvas.example.com

Step 6: First access

Open https://canvas.example.com in your browser. You should see the tldraw editor.

  • Draw a shape and refresh the page. The drawing will be lost because there is no persistence backend.
  • Open the browser's developer console. You should see no network errors.

This is a single-user editor. All state lives in the browser tab.

Maintenance

  • Update tldraw: Run npm install tldraw@latest, rebuild the Docker image (docker compose build), and restart the container (docker compose up -d).
  • Update npm packages: Periodically run npm outdated and npm update, then rebuild.
  • Rebuild on server: Clone your repo on the VPS and run docker compose build && docker compose up -d to deploy new versions.
  • Backups: Since this setup does not persist data, there is no database to back up. If you add persistence, back up the database and any uploaded assets.
  • Monitor: Check docker compose logs for container errors and Nginx access/error logs at /var/log/nginx/.

Key notes

  • No WebSocket upgrade is needed for the static editor. WebSocket sync is only required when you add @tldraw/sync, which runs on Cloudflare Workers, not on this VPS.
  • This guide produces a single-user demo. Adding accounts, document storage, and real-time collaboration are separate concerns that require additional infrastructure.
  • tldraw loads fonts and icons from its CDN by default. For air-gapped deployments, you can self-host these assets.
  • The SDK does not follow semver. When upgrading, always check the tldraw changelog for breaking changes and ensure tldraw and @tldraw/sync versions match if you use both.

Tools mentioned

tldraw

โ†—

A fast collaborative infinite canvas plus a React SDK for building custom canvas experiences.

Trial onlyNo cardOSS

The multiplayer web whiteboard is free; the SDK is free in development and offers a 100-day production trial or hobby license.

ABOUT US

Honest, independent, no fluff.

No paid placements. Just a clear look at what this does, what it costs, and what to know before you commit.

Read moreโ†’

FAQ

Questions, answered.

Is tldraw a self-hosted application?๏ผ‹

No, tldraw is an SDK. You build and deploy your own React application that uses the tldraw components.

Does this guide set up real-time collaboration?๏ผ‹

No. The self-hosted app serves a single-user editor. Multi-user sync requires the @tldraw/sync backend, best deployed on Cloudflare Workers, not on a VPS.

Do I need a license to use tldraw in production?๏ผ‹

Yes. Production use of tldraw requires a license key from tldraw.dev. Check the SDK license terms before publishing.

Does tldraw follow semver?๏ผ‹

No. The SDK does not follow semantic versioning. When using @tldraw/sync, the client and sync package versions must match exactly.