Back to Blog
tutorialIntermediate10 min read

Excalidraw on Proxmox 9 VE LXC

Excalidraw is a free, open-source virtual whiteboard web application for creating hand-drawn style diagrams and sketches. This guide covers installing it on Proxmox VE 9 in an LXC container with a production build.

Excalidraw on Proxmox 9 VE LXC

Excalidraw is a free, open-source virtual whiteboard web application for creating hand-drawn style diagrams and sketches. It's designed for quick brainstorming, flowcharts, architecture diagrams, wireframes, and collaborative drawing sessions with a simple, intuitive interface that mimics sketching on paper.

Prerequisites

  • Proxmox VE 9.0+ installed and running
  • SSH or console access to Proxmox host
  • Available static IP address on your network (e.g., 192.168.10.105)
  • Your network gateway IP address
  • Minimum 3GB RAM and 10GB disk space available (to be safe, use 4GB here if possible)

Network Configuration Required

Before starting, determine:

  • Desired Excalidraw IP: An unused static IP (e.g., 192.168.10.105)
  • Gateway (typically router) IP: Your router's IP (e.g., 192.168.10.1)
  • Subnet Mask: Usually /24 for home networks

Step 1: Create the LXC

Update templates

pveam update

Identify the correct version of Debian

pveam available | grep debian | grep -v turnkey

This guide will use the latest Debian 13 (update for your case)

pveam download local debian-13-standard_13.1-2_amd64.tar.zst

Create the container

pct create 105 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
  --hostname excalidraw \
  --memory 4096 \
  --cores 2 \
  --rootfs local-lvm:10 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.10.105/24,gw=192.168.10.1 \
  --unprivileged 1 \
  --onboot 1 \
  --start 1

Parameters explained:

  • 105: Container ID (must be unique; adjust if already in use)
  • --memory 4096: 4GB RAM (required for building Excalidraw)
  • --cores 2: Two CPU cores (recommended for yarn build process)
  • --rootfs local-lvm:10: 10GB disk space
  • --net0: Static IP configuration for network access
  • --unprivileged 1: Runs as unprivileged container for better security
  • --onboot 1: Auto-start container on system boot
  • --start 1: Start container immediately after creation

Important adjustments:

  • Container ID (105): Change if already in use (check with pct list)
  • IP Address: Replace 192.168.10.105/24 with your desired static IP
  • Gateway: Replace 192.168.10.1 with your network's gateway
  • Storage: Replace local-lvm if using different storage backend
  • Bridge: Replace vmbr0 if using different bridge (check with ip a | grep vmbr)

Verify the container is running successfully

pct status 105

Step 2: Container Setup

Enter the container

pct enter 105

Set locale to avoid package installation warnings. Uncomments the en_US.UTF-8 locale in the configuration file.

sed -i 's/^# en_US.UTF-8 UTF-8$/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
locale-gen && \
update-locale LANG=en_US.UTF-8 && \
exec bash
  • sed -i 's/^# en_US.UTF-8 UTF-8$/en_US.UTF-8 UTF-8/' /etc/locale.gen — Uncomments the en_US.UTF-8 UTF-8 line in the locale configuration file
  • locale-gen — Generates the locale files based on the configuration
  • update-locale LANG=en_US.UTF-8 — Updates the system-wide locale settings
  • export LANG=en_US.UTF-8 — Sets the locale environment variable for the current session
  • exec bash — Refreshes your shell session to apply all the locale changes and prevent warnings during package installations

Update LXC

apt update && apt upgrade -y

Install essential development and dependencies

apt install -y git nano build-essential curl

Step 3: Install Node

Download tar and checksum from https://nodejs.org

wget -P /tmp https://nodejs.org/dist/v24.11.0/node-v24.11.0-linux-x64.tar.xz
wget -P /tmp https://nodejs.org/dist/v24.11.0/SHASUMS256.txt

Verify checksum

grep 'node-v24.11.0-linux-x64.tar.xz' /tmp/SHASUMS256.txt | awk '{print $1 "  /tmp/" $2}' | sha256sum --check

How it works:

  • grep searches the SHASUMS256.txt file and outputs the line containing the filename (format: hash filename)
  • The | pipes that output to sha256sum --check
  • sha256sum --check expects the format hash filename and verifies the file

Extract

tar -xJf /tmp/node-v24.11.0-linux-x64.tar.xz -C /usr/local --strip-components=1

Update paths

echo 'export PATH="/usr/local/bin:$PATH"' >> /etc/profile && \
source /etc/profile

Verify

node --version
npm --version
which node

Install serve

npm install -g serve
which serve

Enable corepack and install yarn

corepack enable && \
echo 'Y' | yarn --version

Clean up

rm /tmp/node-v24.11.0-linux-x64.tar.xz
rm /tmp/SHASUMS256.txt

Step 4: Download and Build Excalidraw

Create installation directory for Excalidraw

mkdir -p /opt/excalidraw

Clone the repository

git clone https://github.com/excalidraw/excalidraw.git /opt/excalidraw

Change to the Excalidraw directory

cd /opt/excalidraw

Install all project dependencies

yarn install

Set Node.js memory limit for build (if it fails even with this, it likely needs even more memory and you may need to increase the container memory temporarily)

export NODE_OPTIONS="--max-old-space-size=3072"

Build Excalidraw for production. This compiles and optimizes the application. The build process is CPU and memory intensive and may take 5-10 minutes.

yarn build:app

Step 5: Create Systemd Service

Create systemd service file for Excalidraw

nano /etc/systemd/system/excalidraw.service

Paste the following service configuration

[Unit]
Description=Excalidraw Service
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/excalidraw/excalidraw-app/build
ExecStart=/usr/local/bin/serve -s /opt/excalidraw/excalidraw-app/build -l 3000
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Service configuration explained:

  • Description: Human-readable service description
  • After=network.target: Ensures network is available before starting
  • Type=simple: Service runs in foreground
  • WorkingDirectory: Directory containing the production build
  • ExecStart: Serves the static production build on port 3000. Uses full path since serve needs its dependencies from the Node modules directory
  • -s: Serve as single-page application (handles client-side routing)
  • -l 3000: Listen on port 3000
  • Restart=always: Automatically restarts if service crashes

Save and exit nano (Ctrl+X, Y, Enter)

Step 6: Enable and Start the Service

Reload systemd daemon to recognize new service file

systemctl daemon-reload

Enable Excalidraw to start automatically on boot

systemctl enable excalidraw

Start the Excalidraw service immediately

systemctl start excalidraw

Check service status and verify it's running

systemctl status excalidraw

Step 7: Verify Installation

Test HTTP access from within container (should see "HTTP/1.1 200 OK")

curl -I http://localhost:3000

Test if your Excalidraw instance is now accessible at (update IP to reflect your LXC IP address):

192.168.10.105:3000

Updating Excalidraw

If you need to update Excalidraw in the future, stop the running service before updating.

systemctl stop excalidraw

Navigate to Excalidraw installation directory

cd /opt/excalidraw

Fetch latest changes from GitHub repository

git fetch origin

Check what would be updated

git log HEAD..origin/main --oneline

Pull and merge latest changes

git pull origin main

Update project dependencies

yarn install

Rebuild the application with new code

export NODE_OPTIONS="--max-old-space-size=3072"
yarn build:app

Restart the service with updated version

systemctl start excalidraw

Verify service started successfully

systemctl status excalidraw

Reverse Proxy (Optional if Caddy is Installed)

If you are running Caddy, you should add a new entry for Excalidraw.

Open the Caddyfile

nano /etc/caddy/Caddyfile

Add new block

draw.hake.rodeo {
    reverse_proxy 192.168.10.105:3000
}

Run the Caddyfile formatter

caddy fmt --overwrite /etc/caddy/Caddyfile

Then restart the Caddy service

systemctl reload caddy