draw.io・Excalidraw・Krokiをセルフホスト比較
draw.ioとExcalidrawはブラウザーで描画し、Krokiはサーバーで処理します。図の内容がサーバー外に出ない範囲と、セルフホストで得られるものを比較します。
運用するセルフホスト型の図作成ツールはどれですか?
セルフホスト型の図作成ツールには、大きく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 は何もコピーを保持しないためです。
サーバー外部へ送信されるもの。 PDF へのエクスポートが最も分かりやすい例です。README では DRAWIO_SELF_CONTAINED を「1 に設定すると、エクスポート要求をエクスポートサーバーへ直接送信せず、Tomcat の ExportProxyServlet(/service/0)経由で処理する」と説明しています。これを逆に読むと、デフォルトではエクスポート要求はデプロイ環境内にとどまりません。このプロジェクトは jgraph/export-server も公開しています。これは「draw.io のスタンドアロン画像エクスポートサーバー」です。自分のハードウェア上でレンダリングしたい場合に使用できます。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 がポート 80 でコンパイル済みの JavaScript バンドルを配信します。公開されているイメージは圧縮時で約 41 MB です(Docker Hub、2026 年 8 月時点)。このサイズから、含まれているものが非常に少ないことが分かります。サーバー上に保存するものがないため、データベース、セッションストア、アップロードディレクトリはありません。
イメージページには、制限が明記されています。「現時点では、自分でインスタンスをセルフホストしても、共有機能やコラボレーション機能はサポートされません」。インターフェースにはボタンが残っているため、その理由を把握しておく必要があります。リアルタイムコラボレーションには、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 にするには、独自の値を指定してフロントエンドをソースからビルドする必要があります。そのサーバーを前提に計画する前に、現在の状態を確認してください。Docker Hub の excalidraw/excalidraw-room イメージは、2026 年 8 月時点で2年以上再ビルドされていませんでした。
実際に描画が保存される場所。 シーンはブラウザーの local storage に、そのデバイス上のその origin 用データとして保存されます。同じ URL をプライベートウィンドウで開くとキャンバスが空になるため、これを最も簡単に確認できます。サイトデータを削除すると描画も削除され、復元元となるサーバー上のコピーはありません。そのため、「Save to...」を使い、JSON 形式の .excalidraw ファイルをバックアップ対象の場所に保存するよう利用者に案内してください。共有インスタンスを使っても、各利用者にはそれぞれ専用の非公開キャンバスが割り当てられます。共有インスタンスは、ホスティングされている個人用のスケッチパッドとして扱ってください。
Kroki: サーバー上でレンダリングする diagrams as code
Kroki は、多数のレンダラーの前段に置く単一の 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 が解決され、コンパニオンが応答したことを確認できます。最初は成功して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> タグから指定できます。上限もあります。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 は Java アプリケーションサーバーの Tomcat を実行するため、誰も描画していない場合でも JVM(Java virtual machine)を使用します。
- 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すべてがアイドル状態のときに 1 回実行し、その後、大きな 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 すると、古い設定のまま動作が続くため、サイトは引き続き利用できますが、変更は反映されません。リバースプロキシの設定を行ごとに解説では、このスニペットに含まれていない header ブロックと証明書のパスを説明しています。
Kroki に Basic 認証を使うのは適切ではありません。その理由を理解しておく必要があります。wiki ページは <img> タグで Kroki の画像を埋め込みます。読者のブラウザーはその URL をサブリソースとして取得しますが、異なる origin に認証情報を送信しません。そのため、リクエストは 401 になり、ページ上のすべての図が壊れた画像として表示されます。代わりに、Kroki をパブリックインターネットから隔離してください。Kroki を wiki コンテナーと同じ Docker network に接続し、ホストには何も公開せず、wiki から service name でアクセスさせます。Compose network で service name を解決する方法で、この構成を実現する仕組みを説明しています。
セルフホスト Wiki の隣で動かす図表ツール
これが、この構成を求める一般的な理由です。Wiki ページには画像が必要ですが、その画像を誰かのノート PC のスクリーンショットにしたい人はいません。
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 待機しています。DRAWIO の値に embed=1&proto=json&spin=1 が含まれていることと、ホスト部分にタイプミスがないことを確認してください。
HTTPS の wiki でエディターのフレームが空白のままになる。 ブラウザーのコンソールには、https:// 内で http:// を読み込もうとした混在コンテンツのエラーが表示されます。ブラウザーがフレームをブロックするため、draw.io は実行されません。エディターも HTTPS で提供してください。
Kroki が 413 Request Entity Too Large を返す。 その文字列を返しているのは Kroki ではなく nginx です。nginx の client_max_body_size のデフォルト値は 1 MB で、Kroki 自身の KROKI_MAX_BODY_SIZE のデフォルト値は 1mb です。そのため、大きな PlantUML ソースでは、値の小さいほうの制限に達します。両方の値を引き上げてください。
graphviz は動作するが、Mermaid は失敗する。 ゲートウェイは正常ですが、コンパニオンに到達できていません。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 を使用してクラウドストレージの保存先を無効にしてください。また、DRAWIO_SELF_CONTAINED=1 を設定して jgraph/export-server を自分で実行しない限り、エクスポート要求はエクスポートサーバーに送信されます。
セルフホストした Excalidraw でコラボレーションが機能しないのはなぜですか?
公式イメージのページには、セルフホストでは「共有またはコラボレーション機能をサポートしない」と記載されています。リアルタイムコラボレーションには別途 excalidraw/excalidraw-room websocket server が必要で、共有リンクにはストレージサービスが必要です。両者のアドレスは、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 ネットワーク上で非公開のまま運用してください。読者のブラウザーからの <img> 要求では、別のオリジンへ認証情報が送信されないため、埋め込んだ図がすべて表示できなくなります。