Как установить и настроить Excalidraw на VPS
Excalidraw — это рукописная доска для набросков, диаграмм и схем, которую любят за минимализм и «бумажный» стиль линий. Публичная версия на excalidraw.com удобна, но хранит ваши доски на чужих серверах, ограничивает совместную работу и периодически недоступна из России без обхода блокировок. Если вы рисуете архитектуру системы, схемы для команды или заметки с NDA-содержимым, разумнее поднять свой инстанс — с собственным доменом, HTTPS и полным контролем над данными. Разберём, как это сделать на VPS через Docker Compose, включая совместную работу в реальном времени.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что нужно перед установкой
Официальный образ Excalidraw (excalidraw/excalidraw) — это статический фронтенд, собранный из исходников проекта. Сам по себе он работает как локальный редактор: рисуйте, сохраняйте .excalidraw-файл на диск, открывайте заново. Для совместной работы в реальном времени (несколько человек на одной доске) нужен ещё и room-сервер excalidraw-room — WebSocket-сервис, который транслирует изменения между участниками комнаты.
Для установки понадобится:
- VPS с 1 vCPU / 1 ГБ RAM — этого достаточно, Excalidraw крайне лёгкий (статика + Node.js WebSocket-сервер);
- Docker и Docker Compose;
- домен или поддомен, направленный на IP сервера (A-запись);
- Nginx или Caddy как reverse proxy для HTTPS.
Если Docker ещё не установлен, поставьте его штатным способом для вашей ОС — на Ubuntu 24.04 это curl -fsSL https://get.docker.com | sh, подробный процесс с нуля описан в статье про установку Docker на Ubuntu 24.04.
Docker Compose: фронтенд и room-сервер
Создайте директорию проекта и файл docker-compose.yml:
mkdir -p ~/excalidraw && cd ~/excalidraw
nano docker-compose.yml
Содержимое:
services:
excalidraw:
image: excalidraw/excalidraw:latest
container_name: excalidraw
restart: unless-stopped
ports:
- "127.0.0.1:5000:80"
environment:
- VITE_APP_WS_SERVER_URL=https://draw.example.com/socket
depends_on:
- excalidraw-room
excalidraw-room:
image: excalidraw/excalidraw-room:latest
container_name: excalidraw-room
restart: unless-stopped
ports:
- "127.0.0.1:5001:80"
Обратите внимание на переменную VITE_APP_WS_SERVER_URL — она указывает фронтенду, куда стучаться за realtime-соединением. Это важная тонкость: официальный образ фронтенда собран с зашитым в бандл адресом excalidraw.com, если ничего не переопределить. Часть версий образа позволяет подменить URL через переменную окружения на старте контейнера (энтрипоинт подменяет строку в собранных JS-файлах), но так делают не все теги — если совместная работа после разворачивания не заработает, самый надёжный вариант это собрать фронтенд из исходников самостоятельно с нужным VITE_APP_WS_SERVER_URL в .env.production, а не полагаться на runtime-подмену в готовом образе.
Замените draw.example.com на ваш реальный домен и запустите:
docker compose up -d
docker compose ps
Оба контейнера должны быть в статусе Up. Проверьте локально:
curl -I http://127.0.0.1:5000
curl -I http://127.0.0.1:5001
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСборка фронтенда из исходников (надёжный вариант)
Если готовый образ не подхватывает переменную окружения для WS-адреса, соберите свой образ. Клонируйте репозиторий и создайте .env.production:
git clone https://github.com/excalidraw/excalidraw.git
cd excalidraw
cat > .env.production <<'EOF'
VITE_APP_WS_SERVER_URL=https://draw.example.com/socket
VITE_APP_BACKEND_V2_GET_URL=
VITE_APP_BACKEND_V2_POST_URL=
EOF
Пустые значения для BACKEND_V2 URL отключают попытки шарить ссылки на excalidraw.com — без своего backend-сервера для хранения сцен эта функция всё равно не заработает, и лучше явно её выключить, чем получать ошибки в консоли браузера.
Соберите Docker-образ:
docker build -t excalidraw-self:latest .
Дальше в docker-compose.yml замените image: excalidraw/excalidraw:latest на image: excalidraw-self:latest и пересоздайте контейнер:
docker compose up -d --build
Такой путь дольше (сборка фронтенда занимает пару минут на слабом VPS), но гарантирует, что realtime-адрес зашит правильно и не зависит от особенностей конкретного релиза готового образа.
Nginx как reverse proxy с WebSocket
Excalidraw и комнатный сервер слушают на localhost, наружу их пускает Nginx. Общая логика настройки reverse proxy на VPS разобрана в статье про Nginx как reverse proxy — здесь же ключевой момент — правильный проброс /socket для WebSocket-соединения.
Создайте конфиг /etc/nginx/sites-available/excalidraw:
server {
listen 80;
server_name draw.example.com;
location / {
proxy_pass http://127.0.0.1:5000;
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;
}
location /socket {
proxy_pass http://127.0.0.1:5001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}
Заголовки Upgrade и Connection "upgrade" обязательны — без них Nginx не пропустит апгрейд HTTP-соединения до WebSocket, и совместное редактирование просто не установит связь (в консоли браузера будет ошибка вида WebSocket connection failed). proxy_read_timeout 86400 держит долгоживущее соединение открытым — без этого Nginx будет рвать сессию по таймауту через минуту простоя.
Активируйте конфиг и проверьте синтаксис:
ln -s /etc/nginx/sites-available/excalidraw /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
HTTPS через Let's Encrypt
WebSocket через Nginx может работать и по HTTP, но браузеры (и сама логика Excalidraw) ожидают wss:// при HTTPS-сайте — незащищённый ws:// на защищённой странице браузер заблокирует политикой mixed content. Поэтому HTTPS здесь не опция, а обязательное условие для совместной работы.
Получите сертификат через certbot:
apt install -y certbot python3-certbot-nginx
certbot --nginx -d draw.example.com
Certbot сам допишет listen 443 ssl и настроит редирект с 80 порта. Подробности о частых граблях с обновлением сертификата и связке certbot + Nginx — в статье про Let's Encrypt SSL на VPS. После получения сертификата VITE_APP_WS_SERVER_URL в вашей сборке уже указывает на https:// (при апгрейде до WebSocket браузер сам превратит это в wss://), так что дополнительно ничего менять не нужно.
Если вы предпочитаете автоматический HTTPS без ручного certbot, вместо связки Nginx + certbot можно взять Caddy — он получает и обновляет сертификаты сам. Сравнение подходов и когда какой выбрать — в статье Caddy или Nginx.
Хранение и синхронизация досок
Здесь стоит понимать архитектуру Excalidraw честно: room-сервер (excalidraw-room) не хранит доски на диске постоянно — это транспортный слой, который синхронизирует изменения между участниками комнаты в реальном времени через WebSocket, используя in-memory состояние. Данные каждой сессии живут, пока активна комната; постоянное хранение обеспечивается иначе — каждый участник хранит копию сцены локально в браузере (localStorage / IndexedDB) и может явно сохранить .excalidraw-файл на диск через меню приложения.
Это значит:
- если вам нужна доска, которая переживает перезапуск сервера и доступна по прямой ссылке всем участникам без ручной пересылки файла — стандартная self-hosted связка «фронтенд + room-сервер» этого не даёт из коробки, для этого в оригинальном проекте существует отдельный backend с хранилищем (Postgres + S3-совместимое хранилище), который в open-source репозитории не публикуется в готовом виде и требует ручной сборки под свою инфраструктуру;
- практичный компромисс для большинства команд — работать в реальном времени в рамках сессии, а важные доски явно экспортировать (
.excalidraw, PNG, SVG) и хранить как обычные файлы, при необходимости — под бэкапом; - если бэкапы файлов с досками важны, настройте их так же, как для любых других данных на сервере — общий подход описан в статье про бэкап Docker volume, если решите примонтировать директорию для хранения экспортов как volume.
Для большинства сценариев — командные брейнштормы, разовые схемы, объяснение архитектуры коллеге — этого достаточно и без полноценного backend с базой данных.
Firewall, обновления и обслуживание
Порты 5000 и 5001 в конфиге выше явно забиндены на 127.0.0.1 — наружу они не торчат, весь внешний трафик идёт только через Nginx на 443 порту. Дополнительно закройте всё лишнее через UFW:
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
Подробнее о настройке файрвола и типичных ошибках — в статье про UFW на VPS.
Обновление до новой версии Excalidraw:
cd ~/excalidraw
docker compose pull
docker compose up -d
Если вы собирали образ из исходников, вместо pull — обновите репозиторий и пересоберите:
cd ~/excalidraw/excalidraw
git pull
docker build -t excalidraw-self:latest .
cd ..
docker compose up -d --build
Логи при проблемах смотрите так:
docker compose logs -f excalidraw
docker compose logs -f excalidraw-room
Чаще всего в логах excalidraw-room видны только подключения/отключения комнат — если WebSocket не устанавливается, проблема обычно на стороне Nginx (не пробросили Upgrade-заголовки) или в mixed content (страница на HTTPS, а WS-адрес зашит с http://).
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли обойтись без Docker и запустить Excalidraw напрямую на Node.js?
Да, оба компонента (фронтенд и room-сервер) — обычные Node/Vite-проекты, их можно собрать и запустить через pm2 или systemd-юнит без контейнеров. Docker Compose просто упрощает управление и обновление, особенно если на сервере крутятся и другие сервисы.
Обязательно ли собирать фронтенд из исходников или можно взять готовый образ?
Попробуйте сначала готовый образ с переменной VITE_APP_WS_SERVER_URL — в части релизов подмена на старте контейнера действительно работает. Если после настройки Nginx совместная работа не устанавливается (в консоли браузера ошибки WebSocket на адрес excalidraw.com), переходите на сборку из исходников — это гарантированно решает проблему.
Хватит ли VPS на 1 ГБ RAM?
Да, с запасом. Фронтенд — статика, room-сервер — лёгкий Node.js-процесс, который держит только открытые WebSocket-соединения. Проблемы с памятью на таком объёме обычно начинаются не от Excalidraw, а от других сервисов на том же сервере.
Как ограничить доступ, чтобы доски не были доступны кому попало по ссылке на комнату?
Проще всего — Basic Auth на уровне Nginx (auth_basic + htpasswd) перед всем location /, либо VPN до сервера. Сам Excalidraw не имеет встроенной системы пользователей и авторизации — это часть его простоты, но и ограничение, которое нужно закрывать на уровне инфраструктуры.
Теряются ли доски при перезапуске контейнера excalidraw-room?
Активные, не сохранённые в файл доски в открытых сейчас комнатах — да, room-сервер хранит состояние сессии в памяти. Уже экспортированные .excalidraw-файлы на диске участников не затрагиваются никак — это отдельный, независимый механизм сохранения.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →