セルフホスト図ツール比較:draw.io、Excalidraw、Kroki
draw.io、Excalidraw、KrokiをVPSで運用する違いを比較します。図がサーバーに届くのはどれか、セルフホストで得られるのがプライバシーか可用性かを整理します。
運用するセルフホスト型の図作成ツールはどれか
セルフホスト型の図作成ツールは、2 つのタイプに分かれます。機能一覧よりも、タイプの違いが重要です。draw.io と Excalidraw はブラウザーアプリケーションです。コンテナは JavaScript を提供し、描画はブラウザーが行うため、サーバーが図の内容を見ることはありません。Kroki はこれと逆です。HTTP 経由で図のテキストを送信すると、画像が返されます。そのため、すべての図が自分のマシンを経由します。
Wiki の横に本格的なエディターを置きたい場合は、draw.io を運用します。手早くスケッチできる場所が必要で、描画したブラウザーの外部には何も保存されないことを許容できる場合は、Excalidraw を運用します。図を、その内容を説明するコードとともに git に置くテキストとして管理する場合は、Kroki を運用します。
図ツールをセルフホストすると実際に何が変わるか
サーバーにアクセスする部分を正確に把握してください。セルフホストによってプライバシーが得られるのか、可用性だけが得られるのかは、この点で決まります。
- draw.io はブラウザーで描画します。コンテナはアプリケーションコードを提供します。ファイルは、エディターで保存先に指定した場所へ保存されます。
- Excalidraw はブラウザーで描画し、現在のシーンをそのブラウザーのローカルストレージに保持します。サーバー側には何も書き込まれません。
- Kroki はサーバーで描画します。図のソースと完成した画像は、どちらもコンテナ内に存在します。
データが管理下のハードウェアに移るのは、3 番目のケースだけです。最初の 2 つでは、セルフホストによって資産の管理権と可用性が得られます。JavaScript は自分のホストから配信されるため、第三者の障害、利用規約の変更、自分のネットワークからの到達不能が発生しても、エディターは動作し続けます。これは一部のチームにとって実際の価値があります。ただし、「図が建物の外へ出ることはない」という主張とは別のものです。
draw.io: データを保存しない公式コンテナ
プロジェクトは独自のイメージを公開しており、README のクイックスタートは 1 行です。
docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawioこれにより、ホストが持つすべてのアドレスでエディターが公開されます。VPS では、公開ポートを loopback にバインドし、リバースプロキシまたは 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 は ufw が管理するチェーンより前に独自の iptables ルールを挿入するためです。そのため、ファイアウォール設定が正しく見えても、ポートはインターネット全体から応答可能になります。仕組みと対処方法は Docker が ufw を迂回して直接ポートを公開する仕組み で説明しています。
エディターを localhost 以外で公開すると、2 つの環境変数が重要になります。
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」と定義しています。これらは viewer、lightbox、embed のコードパスで使われます。https://www.example.com/drawio/ のようなサブパスでエディターを提供する場合は、両方の値にそのサブパスを含める必要があります。アプリはこれらの値から viewer と embed の URL を生成するためです。
永続化はありません。それが設計です。 この Compose ファイルに volume がないのは、コンテナがダイアグラムデータを保持しないためです。.drawio ファイルは、エディターがブラウザーに渡す XML です。選択した保存先によって、保存先が決まります。自分のマシンへのダウンロード、またはエディターを埋め込んだアプリケーションのいずれかです。その保存先をバックアップしてください。VPS 上のフォルダーが保存先なら、保護すべき対象はそのフォルダーと、そこへアクセスするために使う ファイルマネージャー です。draw.io は何もコピーを保持しないためです。
それでもサーバーから出ていくもの。 以前より少なくなっています。エクスポートはどこにも送信されなくなったためです。README では、現在 EXPORT_URL と DRAWIO_SELF_CONTAINED が削除された変数として記載されており、効果はありません。これらは別の jgraph/export-server イメージをエディターに指定していましたが、そのイメージは「提供終了」になっています。現在の entrypoint は window.EXPORT_URL = null を PreConfig.js に書き込み、エクスポートサービスが存在しないことをエディターに通知します。実際には、Export as > PDF を選ぶとブラウザーの印刷ダイアログが開きます。そのため、PDF にダイアグラムのコピーを含めるオプションは表示されず、サーバーでレンダリングする PNG および JPEG のエクスポートも非表示になります。ブラウザーでレンダリングする PNG、JPEG、SVG のエクスポートは引き続き利用できます。ENABLE_DRAWIO_PROXY はデフォルトで無効です。有効にすると、ブラウザーに代わって外部画像 URL を取得する /proxy エンドポイントが有効になります。必要な場合を除き、無効のままにしてください。
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 年 10 月時点)。このサイズから、含まれているものが少ないことが分かります。データベース、セッションストア、アップロードディレクトリはありません。サーバー側に保存するものがないためです。
イメージのページには、制限が明記されています。「現時点では、自分でインスタンスをセルフホストしても、共有機能やコラボレーション機能はサポートされません。」インターフェースにはボタンが残っているため、その理由を把握しておく必要があります。リアルタイムコラボレーションには、excalidraw/excalidraw-room として別途公開されている websocket サーバーが必要です。共有リンクには、暗号化されたシーンを保持するストレージサービスが必要です。これら 2 つの接続先は、ビルド時に Vite の変数(VITE_APP_WS_SERVER_URL、VITE_APP_BACKEND_V2_GET_URL、VITE_APP_BACKEND_V2_POST_URL)としてバンドルへ埋め込まれます。リポジトリの本番用の値は、Excalidraw 自身がホストするサービスを指しています。Vite はビルド時にこれらの値を置き換えるため、最終的には JavaScript 内のリテラル文字列になります。コンテナの環境変数として設定しても、実行時にそれを読み取るコードはないため、何も変わりません。コラボレーションの接続先を自分の room server にするには、自分の値を指定してフロントエンドをソースからビルドする必要があります。そのサーバーを前提に計画する前に、現在の状態を確認してください。2026 年 10 月時点で、Docker Hub にある excalidraw/excalidraw-room イメージの最新の push は 2023 年 12 月でした。
描画データは実際にはどこにあるか
シーンは、その origin に対するブラウザーの local storage に、使用したデバイス上で保存されます。同じ URL をプライベートウィンドウで開けばキャンバスは空になるため、最も簡単に確認できます。サイトデータを消去すると描画も削除され、復元元となるサーバー上のコピーはありません。そのため、「Save to...」を使い、JSON 形式の .excalidraw ファイルをバックアップ対象の場所に保存するよう利用者に伝えてください。共有インスタンスでも、各利用者にはそれぞれ個別の非公開キャンバスが用意されます。たまたまホストされている個人用のスケッチパッドとして扱ってください。
Kroki: コードとして記述した図をサーバーでレンダリング
Kroki は、多数のレンダラーの前段に置く 1 つの HTTP ゲートウェイです。テキストを POST すると、SVG または PNG が返ります。Graphviz、PlantUML、D2 などはゲートウェイのイメージに組み込まれています。Mermaid、BPMN、Excalidraw のレンダリングはコンパニオンコンテナで行うため、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 ネットワーク上のゲートウェイからのみ到達できます。この構成が適切です。wiki を実行するホストが別でない限り、ゲートウェイの行を "127.0.0.1:8000:8000" に変更してください。サーバーで Compose ファイルを作成したことがない場合は、VPS で Docker Compose を実行する方法で、ファイルの配置と docker compose up -d の手順を確認できます。
スモークテストは、異なる理由で失敗するため、この順番で 2 つ実行します。
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 602 つ目のコマンドで SVG が返れば、KROKI_MERMAID_HOST が解決され、コンパニオンが応答したことを確認できます。1 つ目は成功して 2 つ目が失敗する場合、問題は 2 つのコンテナ間にあります。そのため、図の構文を確認する前に docker compose logs kroki を読みます。
GET 形式では図を URL にエンコードします。これにより、wiki はプラグインなしで画像を埋め込めます。ドキュメントには、このエンコーダーが示されています。
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 を使用してください。出力は /{diagram-type}/{output-format}/{encoded-diagram} という形式の URL の末尾に付けます。任意の <img> タグからその URL を参照できます。上限もあります。KROKI_MAX_URI_LENGTH のデフォルト値は 4096 bytes なので、長い図は POST で送信する必要があります。
Kroki に送信するテキストが読み取られるため、重要なのは Kroki のセキュリティ設定です。 KROKI_SAFE_MODE のデフォルト値は SECURE です。これは 3 つのレベルの中で最も制限が厳しい設定です。KROKI_PLANTUML_ALLOW_INCLUDE のデフォルト値は false です。これらのデフォルト値が設定されているのは、PlantUML の !include ディレクティブがレンダラー側からファイルと URL を読み取るためです。誰でもアクセスできるエンドポイントでこれらを緩和すると、コンテナ内で動作するファイルリーダーをインターネットに公開することになります。必要な include パスが分かっている場合を除き、設定は変更しないでください。必要な場合は KROKI_PLANTUML_INCLUDE_PATH でパスを明示します。
小規模 VPS でメモリを圧迫するもの
各コンテナが実行する内容を把握すれば、順序は予測できます。
- Excalidraw イメージは、静的ファイルを配信する nginx です。3 つの中では、他を大きく引き離して最も軽量です。
- draw.io は Tomcat(Java アプリケーションサーバー)を実行するため、誰も描画していない場合でも JVM(Java 仮想マシン)を使用します。
- Kroki gateway も Java サービスで、手動インストール用に jar として提供されています。
- mermaid companion が最も高負荷です。Dockerfile は Chromium をインストールし、
PUPPETEER_EXECUTABLE_PATH=/usr/lib/chromium/chromeを設定します。Mermaid は実際のブラウザエンジンで描画するためです。
したがって、アイドル時の数値だけではほとんど判断できません。重要なのは、図を描画している間に発生するスパイクです。KROKI_MERMAID_MAX_CONCURRENCY のデフォルト値は 6 なので、ブラウザによる描画を同時に 6 件実行できます。公開されている数値を信用するのではなく、自分の環境で測定してください。
docker stats --no-stream
docker system dfまず、すべてがアイドル状態のときに実行します。次に、大きな mermaid 図をループで描画しながら再度実行します。小規模プランでスパイクが問題になる場合は、推測せずに上限を設定してください。Compose サービスにメモリ制限を設定する方法で、構文とコンテナが上限に達した場合の動作を説明しています。mermaid companion を削除する方法も有効です。gateway は、組み込まれているすべての renderer の提供を継続するためです。
どれもユーザー管理機能を提供しないため、前段に認証を置きます
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 の部分です。壊れた設定で reload すると、古い設定が実行されたままになるため、サイトは引き続き動作しますが、変更は反映されません。リバースプロキシの設定を行ごとに説明では、このスニペットに含まれていないヘッダーブロックと証明書のパスを説明しています。
Kroki に Basic 認証を使うのは適切ではありません。その理由を理解しておく必要があります。wiki ページは <img> タグで Kroki の画像を埋め込みます。閲覧者のブラウザーは、その URL をクロスオリジンのサブリソースとして取得します。ブラウザーが Kroki ホスト用に保持している Basic 認証情報は送信されますが、現在のブラウザーはクロスオリジン画像に対してログインプロンプトを表示しません。そのため、Kroki ホストに直接サインインしていない閲覧者は 401 を受け取り、ページ上のすべての図が壊れた画像として表示されます。代わりに、Kroki をパブリックインターネットから切り離します。wiki コンテナと同じ Docker ネットワークに配置し、ホストには何も公開せず、wiki からサービス名でアクセスさせます。Compose ネットワークでサービス名が解決される仕組みで、この構成を実現する方法を説明しています。
セルフホスト Wiki の隣で動かす図
これが、この構成を求める最も一般的な理由です。Wiki ページには画像が必要ですが、その画像を誰かの laptop のスクリーンショットにしたい人はいません。
BookStack には、セルフホスト型エディター用の専用連携機能があります。デフォルトの埋め込み URL は https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1 で、.env の 1 行を変更するとコンテナに切り替えられます。
DRAWIO=https://drawio.example.com/?embed=1&proto=json&spin=1&configure=1クエリ文字列はそのまま正確にコピーしてください。BookStack のドキュメントでは、embed=1&proto=json&spin=1 は「BookStack との連携を機能させるために必要」と説明されています。これは、2 つのページが通信に使用する JSON メッセージプロトコルを選択するためです。同じページでは、stealth=1 について「他の外部サービスを使用したくない場合」と説明されています。セルフホストの目的が外部への送信を停止することであれば、このオプションを追加します。この設定により、BookStack は図をページの近くにある自身の画像ストレージへ保存します。そのため、すでに取得している Wiki のバックアップに図も含まれます。
Wiki 自体をまだ決めていない場合は、先に決定してください。BookStack、Wiki.js、Outline の選択 が先に行うべき判断です。Wiki によって図をページへ添付する方法が決まり、その結果、どのツールを追加するかも決まるためです。
障害のパターンと表示される文字列
BookStack で図のエディターを開くと、読み込み中の表示が終わらない。 スピナーは spin=1 で図のデータを待機しています。proto=json では、エディターは準備ができると init イベントを送信し、図を含む load アクションを応答として待機します。そのため、このやり取りが完了しないとスピナーが表示され続けます。DRAWIO の値に embed=1&proto=json&spin=1 が含まれていることと、ホスト部分に誤記がないことを確認してください。
HTTPS の wiki でエディターのフレームが空白のままになる。 ブラウザーのコンソールには、https:// 内で http:// を読み込もうとしたため、混在コンテンツが発生したと表示されます。ブラウザーがフレームをブロックするため、draw.io は実行されません。エディターを HTTPS で提供してください。
Kroki が 413 Request Entity Too Large を返す。 この正確な文言は nginx がエラー名として表示するものなので、まずプロキシを疑います。ただし、Kroki 自身が 413 を返す場合もあり、Kroki のドキュメントでは Content Too Large と呼ばれています。nginx の client_max_body_size のデフォルト値は 1 MB です。PlantUML はゲートウェイ内でレンダリングされるため、大きな PlantUML ソースではこの制限に達します。Kroki 自身の KROKI_MAX_BODY_SIZE もデフォルトでは 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 のコラボレーションが接続されない。 独自の room server 用にフロントエンドを構築し、それを nginx の背後に配置した場合、プロキシで proxy_set_header Upgrade $http_upgrade; と proxy_set_header Connection "upgrade"; を使って接続をアップグレードする必要があります。これらがないと、websocket のハンドシェイクが通常の HTTP リクエストとして応答され、セッションが開始されません。
ブラウザーのクリーンアップ後にキャンバスが空になる。 シーンはそのデバイスの local storage に保存されており、サーバー側のコピーはありません。対策は設定ではなく習慣です。保持する価値のあるものは、.excalidraw ファイルとしてエクスポートしてください。
FAQ
自己ホストした draw.io では、ダイアグラムを非公開にできますか?
アプリケーションのコードを自分のサーバー上に置けますが、これはデータを非公開にすることとは別です。draw.io はブラウザー上でレンダリングするため、コンテナがダイアグラムを保持することはありません。プライバシーは、ファイルを保存する場所と、許可した外部通信によって決まります。?offline=1 を使用してクラウドストレージの保存先を無効にし、ENABLE_DRAWIO_PROXY は無効のままにしてください。エクスポートでも外部には送信されません。このイメージにはエクスポートサービスが設定されていないため、PDF はブラウザーの印刷ダイアログを経由します。そのため、DRAWIO_SELF_CONTAINED は効果を持たなくなります。
自己ホストした Excalidraw でコラボレーションが機能しないのはなぜですか?
公式のイメージページには、自己ホストでは「共有またはコラボレーション機能をサポートしない」と記載されています。リアルタイムコラボレーションには別途 excalidraw/excalidraw-room websocket サーバーが必要で、共有リンクにはストレージサービスが必要です。両者のアドレスは、VITE_APP_WS_SERVER_URL のような Vite 変数としてビルド時に JavaScript バンドルへ埋め込まれます。そのため、実行中のコンテナに環境変数を設定しても効果はありません。独自の room server を使用する場合は、値を指定してフロントエンドをソースからビルドする必要があります。
自分のサーバーで Mermaid ダイアグラムをレンダリングするにはどうすればよいですか?
Kroki を mermaid companion container とともに実行し、KROKI_MERMAID_HOST にそのサービス名を設定します。次に、ダイアグラムのテキストを /mermaid/svg に POST してレスポンスから SVG を読み取るか、ダイアグラムを GET URL にエンコードして <img> タグの参照先に指定します。Mermaid にはブラウザーエンジンが必要なため、companion は Puppeteer を介して Chromium を操作します。メモリ使用量を見込んでください。KROKI_MERMAID_MAX_CONCURRENCY のデフォルト値は、同時に 6 個のレンダリングです。
これらのツールの前段にパスワードを設定する必要がありますか?
はい。これらのツールにはアカウント機能がないためです。draw.io と Excalidraw は、URL を知っている人に完全なエディターを提供します。Kroki は送信されたテキストをそのままレンダリングします。2 つのエディターには、リバースプロキシで Basic 認証を設定すれば十分です。Kroki は wiki と共有する Docker ネットワーク上で非公開のまま運用してください。別の origin にある <img> では、閲覧者のブラウザーにログインプロンプトが表示されません。そのため、Kroki に直接サインインしていない利用者では、埋め込まれたすべてのダイアグラムが表示されなくなります。