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
/24for home networks
Step 1: Create the LXC
Update templates
pveam updateIdentify the correct version of Debian
pveam available | grep debian | grep -v turnkeyThis guide will use the latest Debian 13 (update for your case)
pveam download local debian-13-standard_13.1-2_amd64.tar.zstCreate 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 1Parameters 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/24with your desired static IP - Gateway: Replace
192.168.10.1with your network's gateway - Storage: Replace
local-lvmif using different storage backend - Bridge: Replace
vmbr0if using different bridge (check withip a | grep vmbr)
Verify the container is running successfully
pct status 105Step 2: Container Setup
Enter the container
pct enter 105Set 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 bashsed -i 's/^# en_US.UTF-8 UTF-8$/en_US.UTF-8 UTF-8/' /etc/locale.gen— Uncomments theen_US.UTF-8 UTF-8line in the locale configuration filelocale-gen— Generates the locale files based on the configurationupdate-locale LANG=en_US.UTF-8— Updates the system-wide locale settingsexport LANG=en_US.UTF-8— Sets the locale environment variable for the current sessionexec bash— Refreshes your shell session to apply all the locale changes and prevent warnings during package installations
Update LXC
apt update && apt upgrade -yInstall essential development and dependencies
apt install -y git nano build-essential curlStep 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.txtVerify checksum
grep 'node-v24.11.0-linux-x64.tar.xz' /tmp/SHASUMS256.txt | awk '{print $1 " /tmp/" $2}' | sha256sum --checkHow it works:
grepsearches the SHASUMS256.txt file and outputs the line containing the filename (format:hash filename)- The
|pipes that output tosha256sum --check sha256sum --checkexpects the formathash filenameand verifies the file
Extract
tar -xJf /tmp/node-v24.11.0-linux-x64.tar.xz -C /usr/local --strip-components=1Update paths
echo 'export PATH="/usr/local/bin:$PATH"' >> /etc/profile && \
source /etc/profileVerify
node --version
npm --version
which nodeInstall serve
npm install -g serve
which serveEnable corepack and install yarn
corepack enable && \
echo 'Y' | yarn --versionClean up
rm /tmp/node-v24.11.0-linux-x64.tar.xz
rm /tmp/SHASUMS256.txtStep 4: Download and Build Excalidraw
Create installation directory for Excalidraw
mkdir -p /opt/excalidrawClone the repository
git clone https://github.com/excalidraw/excalidraw.git /opt/excalidrawChange to the Excalidraw directory
cd /opt/excalidrawInstall all project dependencies
yarn installSet 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:appStep 5: Create Systemd Service
Create systemd service file for Excalidraw
nano /etc/systemd/system/excalidraw.servicePaste 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 descriptionAfter=network.target: Ensures network is available before startingType=simple: Service runs in foregroundWorkingDirectory: Directory containing the production buildExecStart: Serves the static production build on port 3000. Uses full path sinceserveneeds its dependencies from the Node modules directory-s: Serve as single-page application (handles client-side routing)-l 3000: Listen on port 3000Restart=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-reloadEnable Excalidraw to start automatically on boot
systemctl enable excalidrawStart the Excalidraw service immediately
systemctl start excalidrawCheck service status and verify it's running
systemctl status excalidrawStep 7: Verify Installation
Test HTTP access from within container (should see "HTTP/1.1 200 OK")
curl -I http://localhost:3000Test 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 excalidrawNavigate to Excalidraw installation directory
cd /opt/excalidrawFetch latest changes from GitHub repository
git fetch originCheck what would be updated
git log HEAD..origin/main --onelinePull and merge latest changes
git pull origin mainUpdate project dependencies
yarn installRebuild the application with new code
export NODE_OPTIONS="--max-old-space-size=3072"
yarn build:appRestart the service with updated version
systemctl start excalidrawVerify service started successfully
systemctl status excalidrawReverse 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/CaddyfileAdd new block
draw.hake.rodeo {
reverse_proxy 192.168.10.105:3000
}
Run the Caddyfile formatter
caddy fmt --overwrite /etc/caddy/CaddyfileThen restart the Caddy service
systemctl reload caddy