5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ngrok のような HTTP トンネルを docker compose 1つで自前運用できる OSS「gridbase」を試す

5
Last updated at Posted at 2026-10-20

はじめに

gridbase は、手元の Web アプリを公開 URL で見せるための HTTP トンネルを、自分のサーバーで動かす OSS です。
ngrok のようなサービスを、自前で持てる感覚です。

docker compose up 1回で、トンネルのサーバーと管理画面とデータベースが揃います。
管理画面で API キーとサブドメインを作り、クライアントをつなぐと、http://foo.gridbase.localhost/ で手元のアプリが見えるようになります。

この記事の対象読者は、Docker を普段から使っていて、トンネルの仕組みを手元で試したい方です。
もともと SaaS として提供していましたが、もっと多くの人に自由に使ってもらえるよう、OSS として公開しました。
トンネルサーバー(Go)・管理画面(Next.js)・データベース(Directus)を、compose 1つで動くオールインワン構成にまとめています。

Directus は、SQL データベースに REST API と管理画面を付けるソフトウェアです。
gridbase では、ユーザー、API キー、サブドメインの保存先として使います。

所要時間と確認した環境

2026年10月に、次の環境でコマンドの手順を通して確認しました。
手順2〜4の画面操作は、同じ処理を API で実行して確かめ、画面の文言は実装と照合しています。

  • Apple Silicon の Mac
  • Docker Desktop(Docker Engine 28.5.1)
  • Docker Compose v2.40.3

ビルドキャッシュがある状態で、clone から curl での確認まで約2分半でした(30秒の待ち時間を含み、画面操作の時間は含みません)。
ベースイメージを取得済みの状態で、docker compose build --no-cache は36〜40秒ほどでした(筆者の Apple Silicon Mac で計測)。
性能やネットワークが劣る環境では、ビルドに数分を見てください。

この記事で使う主なバージョンは次のとおりです。

  • Directus 11.17.4
  • Next.js 14
  • Go は手元に不要です。ビルドはコンテナ内の golang:1.24 で行い、go.mod の指定は 1.21 です。

どんな場面で使えるか

使い道として想定しているのは、次のような場面です。

  • 手元で開発中の Web アプリを、外部の人に一時的に見せたい
  • ngrok のようなものを、自分のドメインと自分のサーバーで持ちたい
  • チームや社内で、トンネルの API キーとサブドメインを管理しながら共有したい

一方で、向かない場面もあります。
WebSocket やストリーミング応答は使えず、1つのトンネルが同時に処理できるリクエストは1つです。
詳しくは「既知の制約」の節にまとめています。

構成と仕組み

compose には次の5つのサービスが入っています。

サービス 役割
caddy :80 と :443 で受けるリバースプロキシです。
tunnel Go 製のトンネルサーバーです。HTTP 側は :9080、クライアント用のトンネルポートは :9443 です。
web Next.js 製の管理画面です。サインイン、API キー、サブドメインを扱います。
directus データベースとして使う Directus です。既定は SQLite です。
directus-init 起動時に1回だけ動き、スキーマと権限を整えて終了します。

リクエストの流れは次の図のとおりです。

http://foo.gridbase.localhost/ へのリクエストは、次の順に進みます。

  1. ブラウザのリクエストを caddy が受け、tunnel に渡します。
  2. tunnel が、サブドメイン foo を Directus の情報から探します。
  3. クライアントが開いている接続を通して、リクエストを送ります。
  4. クライアントが手元のアプリを呼び、応答を返します。

クライアントは、トンネルサーバーへ自分から接続します。

手元で試す

以下は README.ja.md のクイックスタートと同じコマンドを、同じ順序で並べたものです。
ここからは、手元のマシンで動かす手順です。
手順では、ターミナルを3つ使います。

前提

  • macOS または Linux です。Windows では WSL2 の中で実行してください。
  • Docker(Compose v2)が使えること
  • サンプルアプリ用の Python 3(手持ちの Web アプリでも代わりになります)
  • 手元で空いているポート 80・443・9443

埋まっているポートがあれば、先に .env で別のポートに変えてください。
手順は ポートの変更(英語)にあります。

HTTP のポートを変えた場合(例: 18080)は、次の2点が必要です。

  • 以下の URL すべてに、その番号を付けます。
  • .env に GRIDBASE_ADMIN_URL=http://admin.gridbase.localhost:18080 も設定します。ブラウザで開く URL と一致していないと、サブドメインの作成などの書き込みが 403 になります。

手順6の --server tunnel:9443 は、HTTP のポートを変えても変更不要です。

既定では、gridbase のコンテナのポートは 127.0.0.1 にだけ公開されます。
同じネットワークの他のコンピュータからは届きません。

管理画面は日本語のみです。
以下の画面名とボタン名は、画面に表示されるとおりに書いています。

1. スタックを起動する

git clone https://github.com/nogataka/gridbase-oss.git gridbase && cd gridbase
cp .env.example .env
docker compose up -d --build

初回は、トンネルサーバー(Go)と管理画面(Next.js)をソースからビルドします。
2回目以降の起動は20秒ほどです。

起動したら、コンテナを確認します。
directus-init は1回動いて終了するので、表示するには -a が必要です。

docker compose ps -a

次のような表示になれば成功です。
directus-init は Exited (0)、ほかの4つは healthy です。
表示は抜粋です。実際には IMAGE・COMMAND・PORTS の列も出ます。

NAME                       SERVICE         STATUS
gridbase-caddy-1           caddy           Up 40 seconds (healthy)
gridbase-directus-1        directus        Up 50 seconds (healthy)
gridbase-directus-init-1   directus-init   Exited (0) 30 seconds ago
gridbase-tunnel-1          tunnel          Up 25 seconds (healthy)
gridbase-web-1             web             Up 25 seconds (healthy)

まだ healthy になっていないコンテナがあれば、数秒待ってからもう一度実行してください。

2. サインインする

http://admin.gridbase.localhost/signin を開きます。
.env の GRIDBASE_ADMIN_EMAIL と GRIDBASE_ADMIN_PASSWORD の値を入力して、「ログイン」を押します。

  • 「メールアドレス」: admin@gridbase.localhost
  • 「パスワード」: ChangeMe-local-1234

ダッシュボードが開きます。
ヘッダーには「ダッシュボード」「アカウント」「管理画面」「使い方」が並びます。
画面は「API Key 情報」と「サブドメイン情報」の2つのブロックでできています。

この例の秘密値は、ローカルで試すためのものです。
.env が公開構成を示す場合は、この値のままでは directus-init が起動しません。
公開構成とは、ループバック以外への bind、https、実ドメインのいずれかです。

この検査が見るのは、compose に渡る設定値だけです。
ホスト側のプロキシ、cloudflared、ssh -R、override ファイルで追加した公開経路は検出できません。
公開する前に、秘密値は自分で変えてください。

*.localhost は、curl と現行のブラウザで 127.0.0.1 に解決されます。
DNS の設定は要りません。
解決されない OS では、/etc/hosts に 127.0.0.1 admin.gridbase.localhost foo.gridbase.localhost を追加してください。

3. API キーをコピーする

「API Key 情報」ブロックに、gb_ で始まるキーが表示されています。
右にあるコピーのアイコンを押します。
ツールチップが「クリップボードにコピー」から「コピーしました」に変わります。

キーの欄が - のときは、「API Key生成」を押して、確認ダイアログを承認してください。

4. サブドメインを作る

「サブドメイン情報」ブロックの「新規登録」を押します。
「サブドメインを新規登録」のダイアログに、次のとおり入力します。

項目 入力する値
「トンネルモード」 「HTTP (ウェブ) トンネル」のまま
「ローカルサーバーURL」 http://host.docker.internal:8000
「任意のサブドメイン (3〜16文字)」 foo

「作成」を押すと、緑の通知「成功」が出ます。
表には、foo の行が HTTP と active のバッジ付きで追加されます。

host.docker.internal は、コンテナの中から手元のマシンを指す名前です。
手順6のクライアントはコンテナで動き、アプリはホストで動くので、この名前を使います。

5. ローカルアプリを起動して30秒待つ

2つ目のターミナルを開き、適当な Web アプリを 8000 番で起動します。
このサンプルは、リポジトリではなく専用のフォルダから1ページだけを配信します。
そのため、.env が配信されることはありません。

mkdir -p /tmp/gridbase-demo && echo 'hello from my laptop' > /tmp/gridbase-demo/index.html
python3 -m http.server 8000 --directory /tmp/gridbase-demo

python3 -m http.server は、すべてのネットワークインターフェースで待ち受けます。
起動している間は、同じネットワークの他のコンピュータからもこのページを開けます。
配信するのはデモ用のフォルダだけにして、試し終わったら Ctrl+C で止めてください。

8000 番が使用中(コマンドが Address already in use で止まる)なら、別の番号(例: 8765)にします。
その場合は、上のダイアログとこのコマンドで同じ番号を使ってください。

最初のターミナルに戻り、30秒待ちます。
トンネルサーバーは、およそ30秒ごとにデータベースを読み直します。
そのため、作成したばかりのサブドメインや API キーは、すぐには認識されません。
サーバーがまだ知らないキーで接続したクライアントは、再試行せずに終了します。

sleep 30

6. クライアントを接続する

3つ目のターミナルで、リポジトリのディレクトリから実行します。
<API_KEY> は、コピーしたキーに置き換えてください。
クライアントは Docker の中で動くので、Go は要りません。

docker compose run --rm --no-deps --entrypoint gridbase-tunnel-client tunnel \
  --server tunnel:9443 --api_key <API_KEY>

成功すると、次のような行が出て、そのまま動き続けます。
このターミナルは開いたままにしてください。
warn の行は、ローカルのトンネルポートが平文 TCP のために出るもので、ローカルでは問題ありません。

[gridbase-client] ... build info version=dev commit=unknown ...
[gridbase-client] ... warn: connecting WITHOUT TLS; the API key is sent in clear text. ...
[gridbase-client] ... starting client: server_addr=tunnel:9443 ...
[gridbase-client] ... connected remote=... attempts=1 successes=1

すぐに終了してしまったときは、あと30秒待ってからもう一度実行してください。
サーバーがまだキーを知らなかった可能性があります。
何度も終了するときは、docker compose logs tunnel で同期のエラーが出ていないか確認してください。

7. 届いていることを確認する

最初のターミナルで実行します。

curl http://foo.gridbase.localhost/

手元のアプリのページが返ってきます。
上のサンプルなら hello from my laptop と表示されます。
Python のターミナルには "GET / HTTP/1.1" 200 が出ます。
ブラウザで http://foo.gridbase.localhost/ を開いても確認できます。

エラーコードが返るときは、トラブルシューティング(英語)を見てください。

止め方と片付け方

クライアントと Python サーバーを Ctrl+C で止めます。

データ(ユーザー、API キー、サブドメイン)を残したまま止めるときは、次を実行します。

docker compose down

まっさらな状態からやり直したいときは、データのボリュームも削除します。
ユーザー、API キー、サブドメインがすべて消え、元に戻せません。

docker compose down -v

不要になった .env は、そのあと rm .env で消せます。
docker compose down -v には .env が要ります。
先に消してしまったときは、cp .env.example .env してからやり直してください。

詰まりやすいところ

ポートが埋まっている

80・443・9443 のどれかが使用中だと、起動に失敗します。
.env で別のポートに変えてください。
HTTP のポートを変えたときは、URL にもその番号を付け、GRIDBASE_ADMIN_URL も合わせます。

サンプルアプリの 8000 番が埋まっているときは、別の番号にして、ダイアログとコマンドで同じ番号を使います。

30秒待たずにクライアントをつなぐ

サブドメインや API キーを作った直後は、トンネルサーバーがまだ知りません。
その状態で接続したクライアントは、再試行せずに終了します。
30秒待ってから、もう一度実行してください。

*.localhost が解決されない

curl と現行のブラウザでは、*.localhost は 127.0.0.1 に解決されます。
解決されない OS では、/etc/hosts に次の行を追加してください。

127.0.0.1 admin.gridbase.localhost foo.gridbase.localhost

自分のサーバーで公開するときの要点

ここから先は、手元で動作を確認したあとの話です。
この節は要点だけを書いています。
実際に公開する場合は、必ず docs/production.md(英語)を読んでください。
なお、公開構成の手順は、この記事の筆者が手元で通して確認したものではありません。

  • ドメインは2つ用意します。管理画面とトンネルは、別の登録ドメインに置くことを推奨します。
    • トンネルの利用者は、自分のサブドメインで何を配信するかを制御できます。
    • 同じ登録ドメインだと、同一サイトを使った攻撃が成り立つからです。
  • サブドメイン用のワイルドカード証明書(*.tunnel.example.com の形)には、DNS-01 チャレンジが必要です。
    • そのため、DNS プロバイダーのプラグインを入れた Caddy をビルドします。
  • トンネルポート(9443)は、既定では平文 TCP で、API キーが流れます。証明書を設定して、クライアントに --tls を付けてください。
  • クライアントには --local_server_url を指定して、転送先を固定してください。
    • 指定しないクライアントは、サーバーから渡された転送先を使います。
    • TLS だけでは、転送先を制限できません。
    • ホストで実行するなら http://127.0.0.1:8000、この記事のコンテナ実行なら http://host.docker.internal:8000 を指定します。
  • 秘密値は、最初の起動の前にすべて作り直します。
    • .env が公開構成を示すとき、既定値のままだと directus-init が起動しません。
    • ただし、この検査は compose に渡る設定値だけを見ます。ホスト側のプロキシや cloudflared などで公開する場合は検出できないので、自分で確認してください。
  • 管理画面が使う Next.js 14 には、上流で既知の脆弱性があります。
    • リバースプロキシを置くだけでは緩和されません。
    • VPN、IP 制限、認証プロキシの内側に置いてください。

初回の起動でローカル用の既定値のまま作ったデータベースを、そのまま公開構成に切り替えることは避けてください。
保存済みの管理者アカウントが既定のパスワードのままだと、起動が止まります。

データを引き継ぐ場合は、gridbase 管理者に加えて、Directus 管理者のパスワードと静的トークンも変える必要があります。
.env を書き換えるだけでは、保存済みのアカウントは変わりません。
変え方は Changing credentials after the first start(英語)にあります。
データを作り直す方法もあります。

既知の制約

README の制約をもとに、実装と照合して挙げます。

  • TCP トンネルには外部の待受ポートがありません。最後まで通して動くのは HTTP トンネルだけです。管理画面は TCP を未対応と表示します。
  • 管理画面は Next.js 14 を使っています。公開時の注意は、「自分のサーバーで公開するときの要点」の節にまとめています。
  • 管理画面の UI は日本語のみです。多言語化の仕組みはありません。
  • 1つのトンネルで同時に処理できるリクエストは1つです。本文は全量をメモリに載せます。
  • リクエスト本文を読み終えたあとの、トンネルへの転送処理には、30秒の固定タイムアウトがあります。
  • WebSocket、Server-Sent Events、ストリーミング応答は使えません。
  • サブドメインや API キーの変更がトンネルサーバーに届くまで、通常は30秒ほどかかります。データベースの応答が遅いときや障害時は、もっとかかります。
  • 接続時にサーバーが API キーを知らないと、クライアントは終了します。再起動してください。
  • 管理画面のレート制限はメモリ内です。コンテナ1台での利用を想定しています。

まとめ

gridbase は、ngrok のような HTTP トンネルを、自分のサーバーで動かすための OSS です。
caddy、tunnel(Go)、web(Next.js 14)、directus(11.17.4)が、docker compose 1つにまとまっています。

手元で試す流れは、スタックの起動、サインイン、API キーのコピー、サブドメインの作成、30秒待つ、クライアントの接続の順です。
最後に curl http://foo.gridbase.localhost/ で、手元のアプリのページが返ります。

公開する場合は、ドメインを2つに分け、DNS-01 のワイルドカード証明書とトンネルポートの TLS を用意します。
秘密値の作り直しと、クライアントの転送先の固定も必要です。
管理画面は、VPN などの内側に置いてください。
WebSocket やストリーミングが使えないなど、制約もあります。
まずは手元で動かして、自分の用途に合うかを確かめてください。

参考

※ gridbase 本体は MIT ライセンスですが、同梱の Directus 11.17.4 は BSL 1.1 です。利用者と関連会社の直近12か月の総収益・予算・資金調達額のうち最大のものが500万米ドルを超える場合、本番で使うには、Directus の商用ライセンスが必要です。詳しくは Directus のライセンス本文 を確認してください。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?