Self-hosted инструменты для диаграмм: сравнение
Сравнение draw.io, Excalidraw и Kroki при размещении на собственном VPS. Разбираем, какие данные уходят на сервер, а какие остаются в браузере для обеспечения приватности.
Какой инструмент для создания диаграмм с self-hosted размещением выбрать?
Инструменты для создания диаграмм с self-hosted размещением делятся на два типа, и этот тип важнее, чем список функций. draw.io и Excalidraw — это браузерные приложения: контейнер поставляет JavaScript, ваш браузер выполняет отрисовку, а сервер никогда не видит саму диаграмму. Kroki работает иначе. Вы отправляете ему текст диаграммы по HTTP, а он возвращает изображение, поэтому каждая диаграмма проходит через вашу машину.
Используйте draw.io, если вам нужен полноценный редактор рядом с wiki. Используйте Excalidraw, если вам нужен быстрый инструмент для набросков и вы готовы к тому, что данные не сохраняются вне браузера, в котором вы рисовали. Используйте Kroki, если ваши диаграммы представляют собой текст, который хранится в git рядом с кодом, который они описывают.
Что на самом деле меняет self-hosting инструментов для построения диаграмм
Чётко определите, какие компоненты взаимодействуют с вашим сервером, так как именно этот факт определяет, обеспечивает ли self-hosting конфиденциальность или только доступность.
- draw.io выполняется в браузере. Ваш контейнер раздаёт только код приложения. Файл сохраняется туда, куда вы укажете в редакторе.
- Excalidraw выполняется в браузере и хранит текущую сцену в локальном хранилище браузера. На стороне сервера ничего не записывается.
- Kroki выполняет рендеринг на сервере. Исходный код диаграммы и готовое изображение существуют внутри вашего контейнера.
Только в третьем случае данные перемещаются на оборудование, которое вы контролируете. В первых двух случаях self-hosting обеспечивает контроль над ресурсами и доступность: JavaScript загружается с вашего хоста, поэтому редактор продолжает работать, если у стороннего сервиса происходит сбой, меняются условия использования или он становится недоступен из вашей сети. Для некоторых команд это имеет реальную ценность. Это утверждение отличается от тезиса «диаграмма никогда не покидает периметр сети».
draw.io: официальный контейнер, который ничего не хранит
Проект публикует собственный образ, а инструкция по быстрому запуску в его README состоит из одной строки.
docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawioЭта команда публикует редактор на всех адресах, доступных серверу. На VPS привязывайте публикуемый порт к loopback и обращайтесь к нему через reverse proxy или SSH-туннель.
docker run -d --name drawio --restart unless-stopped -p 127.0.0.1:8080:8080 jgraph/drawioОткройте http://127.0.0.1:8080/?offline=1&https=0 через туннель. В README ?offline=1 назван «функцией безопасности, отключающей поддержку облачных хранилищ». Без этого параметра редактор предлагает Google Drive, OneDrive и GitHub в качестве мест для сохранения, что означает передачу данных на сторонние серверы.
Привязка к 127.0.0.1 защищает порт от доступа из публичного интернета. Обычный -p 8080:8080 не фильтруется через ufw, так как Docker вставляет свои правила iptables перед цепочками, которыми управляет ufw. В результате брандмауэр выглядит настроенным корректно, но порт остается доступным всему миру. В публикации Docker в обход ufw описан механизм этой проблемы и способы её решения.
Две переменные окружения важны, как только редактор перестает работать на localhost.
services:
drawio:
image: jgraph/drawio
container_name: drawio
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
DRAWIO_SERVER_URL: "https://drawio.example.com/"
DRAWIO_BASE_URL: "https://drawio.example.com"Слеш в конце — не опечатка. В README DRAWIO_SERVER_URL определен как «Публичный URL развертывания со слешем в конце», а DRAWIO_BASE_URL как «Тот же URL без слеша в конце», который используется для просмотра, lightbox и путей встраиваемого кода. Если вы размещаете редактор по подпути, например https://www.example.com/drawio/, оба значения должны содержать этот подпуть, так как приложение строит свои URL для просмотра и встраивания на их основе.
Сохранение данных: его нет, и это заложено в архитектуру. В этом файле Compose нет томов (volumes), так как контейнер не хранит данные диаграмм. Файл .drawio — это XML, который редактор передает вашему браузеру, а выбранное вами место сохранения определяет, где он окажется: загрузка на ваш локальный компьютер или приложение, в которое встроен редактор. Создавайте резервные копии этого целевого расположения. Если это папка на VPS, то защищать нужно именно её и файловый менеджер, который вы используете для доступа к ней, так как draw.io не хранит копий файлов.
Что все еще покидает ваш сервер. Меньше, чем раньше, так как экспорт больше никуда не отправляется. В README переменные EXPORT_URL и DRAWIO_SELF_CONTAINED теперь указаны как удаленные и не оказывающие влияния. Ранее они указывали редактору на отдельный образ jgraph/export-server, который «достиг конца жизненного цикла». Точка входа (entrypoint) теперь записывает window.EXPORT_URL = null в PreConfig.js, что сообщает редактору об отсутствии сервиса экспорта. На практике «Экспорт как > PDF» открывает диалоговое окно печати браузера, поэтому опция включения копии диаграммы для PDF не предлагается, а серверный экспорт в PNG и JPEG скрыт. Экспорт в PNG, JPEG и SVG, выполняемый в браузере, продолжает работать. ENABLE_DRAWIO_PROXY по умолчанию выключен и включает эндпоинт /proxy, который загружает внешние URL изображений от имени браузера, поэтому оставьте его выключенным, если в этом нет необходимости.
Excalidraw: статический бандл без серверной части
Официальная страница образа предлагает следующую команду.
docker run --rm -dit --name excalidraw -p 5000:80 excalidraw/excalidraw:latestПеренесите опубликованный порт на loopback по той же причине, что и раньше.
docker run -d --name excalidraw --restart unless-stopped -p 127.0.0.1:5000:80 excalidraw/excalidraw:latestВнутри контейнера Nginx раздает скомпилированный JavaScript-бандл на 80 порту. Опубликованный образ весит около 41 MB в сжатом виде (Docker Hub, октябрь 2026), что говорит о его минимальном размере. Здесь нет базы данных, хранилища сессий или директории для загрузок, так как на сервере нечего хранить.
Страница образа прямо указывает на ограничение: «На данный момент самостоятельный хостинг собственного экземпляра не поддерживает функции совместного использования или совместной работы». Кнопки в интерфейсе остаются, поэтому важно понимать причину. Для совместной работы в реальном времени требуется websocket-сервер, который публикуется отдельно как excalidraw/excalidraw-room. Для ссылки на общий доступ требуется сервис хранения, где будет находиться зашифрованная сцена. Адреса обоих сервисов вшиваются в бандл на этапе сборки как переменные Vite (VITE_APP_WS_SERVER_URL, VITE_APP_BACKEND_V2_GET_URL, VITE_APP_BACKEND_V2_POST_URL), а значения в репозитории по умолчанию указывают на собственные хостинговые сервисы Excalidraw. Vite подставляет эти значения во время сборки, поэтому они становятся обычными строками внутри JavaScript. Установка их в качестве переменных окружения контейнера ничего не меняет, так как никакой код не считывает их во время выполнения. Чтобы направить функции совместной работы на свой сервер комнат, необходимо собрать фронтенд из исходного кода с собственными значениями. Проверьте состояние этого сервера, прежде чем планировать работу с ним: по состоянию на октябрь 2026 года последний пуш образа excalidraw/excalidraw-room на Docker Hub был в декабре 2023 года.
Где на самом деле хранятся рисунки. Сцена находится в локальном хранилище браузера, на конкретном устройстве, для конкретного источника (origin). Откройте тот же URL в приватном окне, и холст будет пустым — это самый быстрый способ убедиться в этом самостоятельно. Очистка данных сайта удаляет рисунок, и серверной копии для восстановления не существует. Поэтому приучите пользователей использовать функцию «Save to...» и хранить файл .excalidraw, который является JSON-файлом, в месте, где выполняется резервное копирование. Общий экземпляр предоставляет каждому человеку его собственный приватный холст. Относитесь к нему как к личному блокноту для эскизов, который просто размещен на сервере.
Kroki: диаграммы как код, отрисовываемые на вашем сервере
Kroki — это единый HTTP-шлюз для множества инструментов отрисовки. Вы отправляете текст методом POST и получаете обратно SVG или PNG. Graphviz, PlantUML, D2 и некоторые другие встроены в образ шлюза. Отрисовка Mermaid, BPMN и Excalidraw выполняется в отдельных контейнерах, поэтому Docker Compose — оптимальный способ запуска. Ниже приведен пример из документации Kroki.
services:
kroki:
image: yuzutech/kroki
depends_on:
- mermaid
- bpmn
- excalidraw
environment:
- KROKI_MERMAID_HOST=mermaid
- KROKI_BPMN_HOST=bpmn
- KROKI_EXCALIDRAW_HOST=excalidraw
ports:
- "8000:8000"
tmpfs:
- /tmp:exec
mermaid:
image: yuzutech/kroki-mermaid
expose:
- "8002"
bpmn:
image: yuzutech/kroki-bpmn
expose:
- "8003"
excalidraw:
image: yuzutech/kroki-excalidraw
expose:
- "8004"expose не публикует порты на хост, поэтому вспомогательные контейнеры доступны только шлюзу внутри сети Compose. Это именно то, что требуется. Измените строку шлюза на "127.0.0.1:8000:8000", если только вики, обращающаяся к нему, не работает на другом хосте. Если вы ранее не создавали файлы Compose на сервере, в запуске Docker Compose на VPS описана структура файлов и цикл docker compose up -d.
Выполните два проверочных теста в указанном порядке, так как они могут завершиться ошибкой по разным причинам.
curl -s -X POST http://127.0.0.1:8000/graphviz/svg \
-H 'Content-Type: text/plain' \
--data-binary 'digraph G {Hello->World}' | head -c 60Graphviz работает внутри шлюза, поэтому SVG-документ в данном случае подтверждает работоспособность самого шлюза. Теперь проверьте путь, проходящий между контейнерами.
curl -s -X POST http://127.0.0.1:8000/mermaid/svg \
-H 'Content-Type: text/plain' \
--data-binary 'graph TD; A-->B;' | head -c 60SVG, полученный в результате второй команды, подтверждает, что KROKI_MERMAID_HOST успешно разрешилось и вспомогательный контейнер ответил. Если первая команда работает, а вторая — нет, проблема кроется во взаимодействии между контейнерами, поэтому изучите docker compose logs kroki, прежде чем менять синтаксис диаграммы.
Форма GET кодирует диаграмму в URL, что позволяет вики встраивать изображения без использования плагинов. Документация предоставляет соответствующий кодировщик.
cat hello.dot | python -c "import sys; import base64; import zlib; print(base64.urlsafe_b64encode(zlib.compress(sys.stdin.read().encode('utf-8'), 9)).decode('ascii'))"В Ubuntu эта команда выводит python: command not found, так как в системе поставляется python3 и отсутствует неверсионированный python. Используйте python3. Результат добавляется в конец URL вида /{diagram-type}/{output-format}/{encoded-diagram}, и на него может ссылаться любой тег <img>. Существует ограничение: KROKI_MAX_URI_LENGTH по умолчанию составляет 4096 байт, поэтому длинные диаграммы необходимо отправлять методом POST.
Kroki считывает отправленный вами текст, поэтому настройки безопасности имеют решающее значение. KROKI_SAFE_MODE по умолчанию имеет значение SECURE — это самый строгий из трех уровней, а KROKI_PLANTUML_ALLOW_INCLUDE по умолчанию имеет значение false. Эти значения по умолчанию установлены потому, что директива !include в PlantUML считывает файлы и URL с точки зрения отрисовщика. Если ослабить их на эндпоинте, доступном извне, вы предоставите любому пользователю интернета возможность считывать файлы изнутри вашего контейнера. Не меняйте их, пока не поймете, какой путь для включения (include) вам необходим, а затем укажите его с помощью KROKI_PLANTUML_INCLUDE_PATH.
Память: что создает нагрузку на небольшом VPS
Порядок потребления ресурсов предсказуем, если знать, что выполняет каждый контейнер.
- Образ Excalidraw — это nginx, отдающий статические файлы. Это самый легковесный из трех вариантов с большим отрывом.
- draw.io работает на Tomcat, сервере приложений Java, поэтому он всегда содержит JVM (Java virtual machine), независимо от того, рисует кто-то в данный момент или нет.
- Шлюз Kroki — это тоже Java-сервис, поставляемый в виде jar-файла для ручной установки.
- Компонент mermaid — самый ресурсоемкий. Его Dockerfile устанавливает Chromium и задает
PUPPETEER_EXECUTABLE_PATH=/usr/lib/chromium/chrome, так как Mermaid выполняет рендеринг в полноценном движке браузера.
Поэтому показатели в режиме ожидания дают мало информации. Важное значение имеет всплеск потребления во время рендеринга диаграммы, а KROKI_MERMAID_MAX_CONCURRENCY по умолчанию равно 6, что позволяет выполнять до шести процессов рендеринга в браузере одновременно. Измерьте показатели на своем сервере, вместо того чтобы доверять опубликованным цифрам.
docker stats --no-stream
docker system dfЗапустите первую команду, пока система простаивает, а затем повторите её во время циклического рендеринга большой диаграммы mermaid. Если всплеск потребления критичен для вашего тарифного плана, лучше ограничить ресурсы, чем гадать: установка лимитов памяти для сервиса в Compose содержит синтаксис и описание того, что происходит, когда контейнер достигает своего предела. Отказ от компонента mermaid также является допустимым решением, так как шлюз продолжает обслуживать все встроенные в него средства рендеринга.
Ни один из этих инструментов не поставляется с моделью пользователей, поэтому добавьте её перед ними
В draw.io нет учетных записей. В Excalidraw нет учетных записей. Kroki отвечает на любой запрос, который до него доходит. Любая авторизация должна выполняться на уровне прокси.
sudo apt update && sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd alicehtpasswd -c создает файл и перезаписывает существующий, поэтому используйте -c только при первом запуске, а затем никогда больше.
server {
listen 443 ssl;
server_name drawio.example.com;
location / {
auth_basic "diagrams";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Примените конфигурацию с помощью sudo nginx -t && sudo systemctl reload nginx. Часть nginx -t является важной: перезагрузка при наличии ошибок в конфигурации оставляет работать старую версию, поэтому сайт продолжает функционировать, а ваши изменения не вступают в силу. В разборе конфигурации reverse proxy по строкам описаны блок заголовков и пути к сертификатам, которые не включены в этот фрагмент.
Basic authentication — неподходящий инструмент для Kroki, и важно понимать почему. Страница вики встраивает изображение Kroki с помощью тега <img>. Браузер читателя запрашивает этот URL как кросс-доменный подресурс. Он прикрепит учетные данные Basic, если они уже сохранены для хоста Kroki, но современные браузеры не показывают окно входа для кросс-доменных изображений. В результате любой читатель, который не авторизовался на хосте Kroki напрямую, получит 401, и все диаграммы на странице будут отображаться как битые изображения. Вместо этого ограничьте доступ к Kroki из публичного интернета. Поместите его в ту же сеть Docker, что и контейнер с вики, и позвольте вики обращаться к нему по имени сервиса, не публикуя порты на хост-машине. Как сети Compose разрешают имена сервисов — это именно то, что заставляет данную схему работать.
Диаграммы, хранящиеся рядом с self-hosted вики
Это основная причина, по которой пользователям требуется подобная функциональность. Странице вики нужно изображение, и никто не хочет использовать скриншот с чьего-либо ноутбука.
В BookStack предусмотрена первоклассная интеграция с self-hosted редактором. URL для встраивания по умолчанию — https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1, и одна строка в .env перенаправляет его на ваш контейнер.
DRAWIO=https://drawio.example.com/?embed=1&proto=json&spin=1&configure=1Скопируйте строку запроса в точности. В документации BookStack указано, что embed=1&proto=json&spin=1 «необходимы для работы интеграции с BookStack», поскольку они выбирают протокол обмена сообщениями JSON, который используется для взаимодействия между страницами. На той же странице приведена ссылка на stealth=1 «если вы не хотите использовать другие внешние сервисы» — это параметр, который следует добавить, если целью self-hosting было именно прекращение исходящих вызовов. После настройки BookStack сохраняет рисунок в собственное хранилище изображений рядом со страницей, поэтому резервная копия вики, которую вы уже делаете, одновременно является и резервной копией диаграмм.
Если вы еще не выбрали саму вики, сделайте это в первую очередь. Выбор между BookStack, Wiki.js и Outline — это более раннее решение, так как именно вики определяет способ прикрепления диаграммы к странице и, следовательно, выбор инструмента для интеграции.
Режимы сбоев и сообщения, которые вы увидите
Редактор схем в BookStack открывается и бесконечно отображает индикатор загрузки. Индикатор spin=1 ожидает данные диаграммы. При использовании proto=json редактор отправляет событие init, когда он готов, и ожидает в ответ действие load, содержащее диаграмму. Если этот обмен данными не завершается, индикатор продолжает отображаться. Убедитесь, что embed=1&proto=json&spin=1 присутствует в вашем значении DRAWIO и в части с именем хоста нет опечаток.
Фрейм редактора остается пустым в wiki с HTTPS. Консоль браузера сообщает о смешанном контенте (mixed content) при попытке загрузить http:// внутри https://. Браузер блокирует фрейм, и draw.io не запускается. Обеспечьте работу редактора через HTTPS.
Kroki возвращает 413 Request Entity Too Large. Эта формулировка является стандартным сообщением об ошибке в nginx, поэтому в первую очередь следует проверить прокси. Однако Kroki может самостоятельно отправлять ошибку 413, которую в документации называют Content Too Large. Значение client_max_body_size по умолчанию в nginx составляет 1 MB, и именно в этот лимит упирается большой исходный код PlantUML, так как PlantUML выполняет рендеринг внутри шлюза. Собственный параметр KROKI_MAX_BODY_SIZE в Kroki также имеет значение по умолчанию 1mb, но он существует только в контейнерах-компаньонах для diagrams.net, Excalidraw и Mermaid, поэтому большой исходный код Mermaid может достичь любого из этих лимитов. Сначала увеличьте client_max_body_size, а также увеличьте KROKI_MAX_BODY_SIZE на контейнере-компаньоне, если диаграмма проходит через него.
Mermaid не работает, а graphviz работает. Шлюз исправен, но связь с компаньоном отсутствует. Проверьте, запущен ли сервис с помощью docker compose ps, затем убедитесь, что KROKI_MERMAID_HOST совпадает с именем сервиса, так как по умолчанию оно равно 127.0.0.1, что внутри контейнера шлюза означает сам шлюз.
Совместная работа в Excalidraw не подключается. Если вы собрали фронтенд для собственного сервера комнат и разместили его за nginx, прокси должен обновлять соединение с помощью proxy_set_header Upgrade $http_upgrade; и proxy_set_header Connection "upgrade";. Без них рукопожатие websocket обрабатывается как обычный HTTP-запрос, и сессия не начинается.
Холст пуст после очистки данных браузера. Сцена хранилась в локальном хранилище устройства, и серверной копии не существует. Решение заключается в привычке, а не в настройках: экспортируйте файл .excalidraw для всего, что важно сохранить.
FAQ
Гарантирует ли self-hosting draw.io конфиденциальность моих диаграмм?
Это сохраняет код приложения на вашем сервере, что не тождественно конфиденциальности данных. draw.io отрисовывается в вашем браузере, поэтому контейнер вообще не хранит диаграммы. Конфиденциальность зависит от того, где вы сохраняете файл и какие исходящие вызовы остаются разрешёнными. Используйте ?offline=1 для отключения облачных хранилищ и оставьте ENABLE_DRAWIO_PROXY выключенным. Экспорт также ничего не отправляет наружу: изображение не использует сторонний сервис экспорта, поэтому PDF создаётся через диалог печати браузера, а DRAWIO_SELF_CONTAINED больше не оказывает никакого влияния.
Почему совместная работа не работает в моём self-hosted Excalidraw?
На официальной странице образа указано, что self-hosting «не поддерживает функции совместного использования или совместной работы». Для работы в реальном времени требуется отдельный websocket-сервер excalidraw/excalidraw-room, а для ссылок на общий доступ — сервис хранения. Адреса обоих компонентов вшиваются в JavaScript-бандл на этапе сборки в виде переменных Vite, таких как VITE_APP_WS_SERVER_URL, поэтому установка переменной окружения для запущенного контейнера не даст эффекта. Использование собственного сервера комнат означает сборку фронтенда из исходного кода с вашими значениями.
Как отрисовывать диаграммы Mermaid на собственном сервере?
Запустите Kroki вместе с сопутствующим контейнером mermaid и установите KROKI_MERMAID_HOST на имя этого сервиса. Затем отправьте текст диаграммы методом POST на /mermaid/svg и получите SVG из ответа, либо закодируйте диаграмму в URL для GET-запроса и укажите его в теге <img>. Сопутствующий сервис управляет Chromium через Puppeteer, так как Mermaid требует движок браузера, поэтому учитывайте потребление памяти: KROKI_MERMAID_MAX_CONCURRENCY по умолчанию настроен на шесть одновременных отрисовок.
Нужен ли пароль перед этими инструментами?
Да, так как ни в одном из них нет системы учётных записей. draw.io и Excalidraw предоставляют полный редактор любому, кто узнает URL, а Kroki отрисовывает любой отправленный ему текст. Базовой аутентификации на reverse proxy достаточно для обоих редакторов. Для Kroki оставьте сервис недоступным извне, разместив его в Docker network совместно с вики, так как браузер пользователя не покажет запрос на вход для <img> с другого источника, и все встроенные диаграммы перестанут отображаться у тех, кто не авторизовался в Kroki напрямую.