セルフホスト
HacoL は Compose の6サービスで動きます。既定の経路は、手元の Mac や常時オンの機械で Cloudflare Tunnel を使う経路です。ホストが 80 と 443 を受けられるときは、VPS で 80 と 443 を開ける経路も使えます。どの経路でも、Compose の6サービスは同じです。
このページの内容
Compose の6サービス#
postgres- Synapse、ブリッジ、Context の DB(postgres:17-alpine)
synapse- Matrix のホームサーバー(v1.159.0)
line-bridge- LINE のブリッジ(beeper/line を固定した版。イメージはキットのスクリプトで作ります)
whatsapp-bridge- WhatsApp のブリッジ(mautrix/whatsapp を固定した版。イメージはキットのスクリプトで作ります)
context-app- 管理画面と REST API(MCP が呼ぶ API)
caddy- リバースプロキシ(caddy:2.11.4-alpine)
7つ目のサービス、Redis、キューは追加しません。ホストに公開するのは caddy のポートだけです。PostgreSQL は1つのコンテナで4つのデータベースを持ちます。
| DB | 用途 |
|---|---|
synapse | ホームサーバー |
line_bridge | LINE のブリッジのセッションとポータル |
whatsapp_bridge | WhatsApp のブリッジのセッションとポータル |
line_context | 正規化した部屋、メッセージ、監査ログ |
前提#
- 常時オンのホストを用意します。スリープしない Mac や PC、または VPS です。VPS の場合は Ubuntu 24.04 で動作を確認しています。
- 手元の Mac では OrbStack を使います。代わりに Docker Desktop か Docker Engine も使えます。どれも Docker Compose v2.24 以上が必要です。
- ドメインを用意します(apex に加えて matrix 用と context 用)。
- Cloudflare のアカウントを用意します。Tunnel を使う経路では必須です。無料のプランで使えます。
- LINE にメールアドレスとパスワードでログインできる、個人のアカウントを用意します。
- WhatsApp を使う場合は、WhatsApp を入れたスマートフォンを用意します(QR の読み取りに使います)。
経路B(既定)手元の機械と Cloudflare Tunnel
Mac や、ポートを開けられない常時オンの機械で使います。Caddy は 127.0.0.1 だけで待ち受け、外からは Tunnel を通してだけ届きます。手順
経路A VPS で 80 と 443 を開ける
ホストが 80 と 443 を直接受けられるときの経路です。Caddy が Let's Encrypt で証明書を取ります。手順
どちらの経路でも、Compose の6サービスは同じです。.env の PUBLIC_PATH と COMPOSE_FILE で切り替えます。
共通の準備#
手順1 .env を作る#
bashcp .env.example .env chmod 600 .env.envのCHANGE_ME_LONG_RANDOMをすべて置き換えます。値は次のコマンドで作ります。Postgres の初期化 SQL にパスワードを埋め込むので、シングルクォートを含まない値を使います。bashopenssl rand -base64 36管理画面のパスワード(
ADMIN_PASSWORD)、セッションの秘密(SESSION_SECRET)、MCP が使う API キー(CONTEXT_READ_API_KEYとCONTEXT_AGENT_API_KEY)も.envに入れます。送信の停止スイッチの初期値はSEND_ENABLED=false(送信しない)です。部屋を意図して許可するまで、このままにします。やってはいけない.env の中身をチャットに貼らないでください。
手順2 ブリッジのイメージを作る#
LINE と WhatsApp のブリッジのイメージは、bootstrap の前に作ります。bootstrap と Compose はブリッジをビルドしません。版は
.envのLINE_BRIDGE_COMMITとWHATSAPP_BRIDGE_COMMITに固定しています。bash./scripts/build-line-bridge-image.sh ./scripts/build-whatsapp-bridge-image.shホストのメモリが少ないとき(4GB ほどの VPS など)は、別のマシンで
--saveを付けて tar に書き出し、ホストに運んで読み込みます。bash./scripts/build-line-bridge-image.sh --save line-bridge-pinned.tar ./scripts/build-whatsapp-bridge-image.sh --save whatsapp-bridge-pinned.tar # ホストで docker load -i line-bridge-pinned.tar docker load -i whatsapp-bridge-pinned.tar
経路B 手元の機械で Cloudflare Tunnel を使う#
- OrbStack の対応は macOS 14 以降です。OrbStack を業務で使う場合は、OrbStack の有料ライセンスが必要です。
- Mac や PC がスリープすると、Tunnel も Docker も止まり、メッセージを受け取りません。電源につなぎ、スリープしない設定にします。
- cloudflared は Compose のサービスにせず、ホストで動かします。
手順1 OrbStack を入れる#
公式サイトからアプリをダウンロードするか、Homebrew で入れます。
bashbrew install --cask orbstackOrbStack を起動し、docker と docker compose が使えることを確かめます。
bashdocker version docker compose version手順2 .env を経路B の値にする#
.env.exampleの既定は経路B です。example.comを自分のドメインに置き換えます。.envPUBLIC_PATH=tunnel COMPOSE_FILE=compose.yml:compose.tunnel.yml CADDY_HTTP_PORT=18080 BASE_DOMAIN=example.com MATRIX_HOST=matrix.example.com CONTEXT_HOST=context.example.com MATRIX_SERVER_NAME=matrix.example.com手順3 bootstrap を実行する#
bash./scripts/bootstrap.shbootstrap は Synapse に owner ユーザーを作り、LINE と WhatsApp の appservice の登録を Synapse に入れ、6サービスを起動します。経路B では、最後に
http://127.0.0.1:18080の Caddy が応答すれば完了です。Tunnel がまだでも止まりません。注意bootstrap は再実行しても既存の owner とトークンを残します。データがある環境では、実行の前に backup.sh を取ります。
手順4 Tunnel を作り、DNS を向ける#
bashcloudflared tunnel login cloudflared tunnel create <tunnel-name> cloudflared tunnel route dns <tunnel-name> context.example.com cloudflared tunnel route dns <tunnel-name> matrix.example.comroute dnsは Cloudflare に Proxied の CNAME を作ります。A レコードは要りません。設定ファイルは、キットのinfra/cloudflared/config.example.ymlを写して、Tunnel の ID、認証情報のファイルの場所、ホスト名を自分の値に置き換えます。注意認証情報のファイルの中身は秘密です。チャットに貼りません。cloudflared tunnel login で別のゾーンを選ぶと、使うドメインの DNS が更新されません。ダッシュボードで、使うドメインのゾーンを確認します。
手順5 Tunnel を起動する#
bashCLOUDFLARE_TUNNEL_NAME=<tunnel-name> ./scripts/tunnel-run.sh別のターミナルで確認します。
bash./scripts/doctor.sh curl -sI https://context.example.com/health | grep -i cf-ray常に動かすときは、cloudflared をサービスとして登録します(Mac では launchd に登録されます)。
bashsudo cloudflared service install --config "<config-file>"
待ち受けるポート
| ポート | 用途 |
|---|---|
| 127.0.0.1:18080 | cloudflared の転送先(HTTP)。管理画面のログインには使いません |
| 127.0.0.1:18443 | 同じホストからの管理画面のログイン(HTTPS。証明書は Caddy の内部 CA) |
やってはいけない18080 と 18443 を外へ出さないでください。ルーターのポート転送、ngrok、ssh -R などで公開しません。18080 に接続するのは、同じホストの cloudflared だけです。
Tunnel のよくある失敗
| 症状 | 意味 | まずやること |
|---|---|---|
| Cloudflare の 1033 か 530 | Tunnel が止まっています(スリープ、cloudflared の停止) | Tunnel を起動し、docker compose ps を確認する |
| 502 | Tunnel は動いていますが、Caddy や Compose が応答していません | 127.0.0.1:18080 にローカルで curl し、docker compose logs を見る |
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Host: context.example.com' http://127.0.0.1:18080/health経路A VPS で 80 と 443 を開ける#
手順1 DNS とファイアウォール#
matrixとcontextの A レコードを VPS に向けます。Cloudflare を使う場合も、この経路ではプロキシを使わない設定(DNS only)にします。VPS に IPv6 が無ければ AAAA は足しません。- 開けるのは TCP 22、80、443 だけです。5432、8008、29318、29322、3000、18080 は公開しません。
- Caddy が Let's Encrypt で証明書を取ります。
手順2 .env を経路A の値にする#
.envPUBLIC_PATH=public COMPOSE_FILE=compose.yml手順3 bootstrap を実行する#
bashchmod +x scripts/*.sh ./scripts/bootstrap.shDNS が VPS を向いていないときは、bootstrap が止まって直し方を表示します。
LINE と WhatsApp にログインする#
非公式のブリッジですLINE と WhatsApp のブリッジは非公式です。LINE 公式の Messaging API ではなく、LINE ヤフー社との提携もありません。upstream の変更で止まることがあり、アカウントがログアウトされたり、利用を制限されたり、停止されたりするおそれがあります。動き続けることやログインの継続は約束しません。
| 管理画面のページ | すること |
|---|---|
/setup/line(LINEの接続) | LINE に登録したメールアドレスとパスワードを入力して「ログイン」を押します。表示された PIN を、スマートフォンの LINE に入力します。 |
/setup/link | LINE の PIN と WhatsApp の QR を表示します。「WhatsAppのログインを始める」を押し、QR をスマートフォンの WhatsApp で読み取ります。 |
/setup/invites(トークルーム) | トークルームへの参加の進み具合を表示します。AUTO_ACCEPT_INVITES=true(既定)のとき、ブリッジからの招待は自動で参加します。ほかの招待は、「参加する」を押すまで取り込みません。 |
- 経路B では、同じホストのブラウザで
https://127.0.0.1:18443/setup/lineを開きます。平文の HTTP では管理画面にログインできません。 - 資格情報は、管理画面の入力欄にだけ入力します。ブリッジが Unknown command と返したときは、ログインの案内の期限が切れています。「やめる」を押してから、もう一度送ります。
- 部屋に参加しても、送信は許可されません。読むための参加と、送信の許可は別の操作です。
- 送信を許可する前に、安全なテスト用のチャットで、自分の LINE アカウントのやりとりとして取り込まれることを確認します。
- LINE アプリで自分が送ったメッセージが受信として表示されるときは、
.envのLINE_OWNER_GHOST_MXIDSに自分の LINE ghost の MXID を書きます(FAQ)。
やってはいけないLINE の Chrome 拡張は使わないでください。資格情報をチャットに書かないでください。
確認する#
診断のスクリプトを実行します。最後の行で結果を判断します。FAIL が1つでもあると、終了コードが1になります。WARN だけなら PASS です。
./scripts/doctor.shDoctor finished with PASS (N warnings) → 終了コード 0
Doctor finished with FAIL → 終了コード 1続けて smoke-test を実行します。実在しない部屋の ID だけを使い、LINE や WhatsApp へ実際には送信しません。read キーで送信できないこと、許可していない部屋への送信が拒否されることなどを確かめます。
./scripts/smoke-test.sh最後の行が Smoke test passed (no real LINE or WhatsApp send was attempted). なら成功です。あわせて次を確認します。
https://context.example.com/healthが 200 を返すhttps://matrix.example.com/_matrix/client/versionsが 200 を返すdocker compose psで6サービスが Up- MCP の
line_list_roomsが部屋の一覧を返す
注意サービスを上げ直すときは docker compose up -d を使い、--build は付けません。/health は matrix_token: present|invalid も返しますが、トークンの値は返しません。
ログの確認#
docker compose ps
docker compose logs --tail=200 postgres synapse line-bridge whatsapp-bridge context-app caddy| 症状 | 意味 | まずやること |
|---|---|---|
| MCP の 401 | キーがずれています | API キーを入れ替えるときの手順を実行する |
| doctor が FAIL | 6サービス、Synapse への登録、Context の応答などのどれかに問題があります | FAIL の行を読み、docker compose logs を見る |
| 管理画面のログインが 429 | 同じ接続元から15分間に5回失敗しました | 15分待つか、context-app を再起動する |
バックアップと更新#
./scripts/backup.sh
BACKUP_SECRETS_PASSPHRASE='<long-random-passphrase>' ./scripts/backup.sh --include-secrets-encrypted- オプションなしの backup.sh は、4つの DB のダンプを書き出します。ブリッジのセッション、そのほかの資格情報、
.env、.runtime.env、署名鍵、メディアは含めません。このバックアップから戻したときは、LINE と WhatsApp にログインし直します。 --include-secrets-encryptedを付けると、セッション、秘密のファイル、メディアを含めて暗号化します。BACKUP_SECRETS_PASSPHRASEを設定します。戻すときも同じパスフレーズが要ります。- Context の DB のダンプには、メッセージの本文が平文で入ります。バックアップは秘密と同じように扱います。
LINE_CONTEXT_DESTROY=yes BACKUP_SECRETS_PASSPHRASE='<long-random-passphrase>' \
./scripts/restore.sh --yes-i-understand --in-place backups/<stamp>- restore.sh は、
--in-place(いまの構成のデータを置き換える)か--project(別のプロジェクトに戻す)のどちらか1つを指定したときだけ動きます。データを上書きするので、--yes-i-understandとLINE_CONTEXT_DESTROY=yesが要ります。
BACKUP_SECRETS_PASSPHRASE='<long-random-passphrase>' ./scripts/update-line-bridge.sh <40-character-commit-sha>
BACKUP_SECRETS_PASSPHRASE='<long-random-passphrase>' ./scripts/update-whatsapp-bridge.sh <40-character-commit-sha>- ブリッジを更新するときは、40文字のコミット SHA を渡します。更新のスクリプトは main を自動では追いません。
BACKUP_SECRETS_PASSPHRASEが無いと始まらず、始める前に暗号化したバックアップを取ります。 - コンテナを更新するときは、6サービスのまま版の固定を更新し、起動のあと
./scripts/doctor.sh、health、MCP を確認します。 - 2026-09-30 より前に作った環境には、管理用の部屋の本文が残っていることがあります。バックアップを取ってから、
./scripts/purge-sensitive-bodies.sh --yes-i-understandで一度だけ消せます。
別のマシンから移すときの注意#
- LINE と WhatsApp は、2台で同時にログインすると片方の接続が切れやすくなります。移すときは、元のマシンのブリッジを止め、データを最後に同期してから、新しいマシンで起動します。
- 同期するのは
data/postgres、data/synapse、data/line-bridge、data/whatsapp-bridge、.env、.runtime.env、必要ならbackups/です。.envと.runtime.envは、チャットを通さずに別の経路でコピーします。 - macOS に付いている rsync には
--info=progress2が無いことがあるので、--progressを使います。
人だけが行う操作#
- 支払いと購入
- アカウントの作成(Cloudflare、VPS)と、LINE のメールアドレスとパスワードの入力、WhatsApp の QR の読み取り
- ドメインの選択と購入
- 2段階認証と OS の同意
やってはいけない秘密の値を AI のチャットに貼らないでください。
サポートの範囲#
対象
1人の運用者が動かす、1つの構成です。
対象外
マルチテナントの運用、他社向けの再販 SaaS、Compose へのサービスの追加です。標準の構成では Remote MCP を公開しません。
最終更新 2026年10月20日