はじめに
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/ へのリクエストは、次の順に進みます。
- ブラウザのリクエストを caddy が受け、tunnel に渡します。
- tunnel が、サブドメイン
fooを Directus の情報から探します。 - クライアントが開いている接続を通して、リクエストを送ります。
- クライアントが手元のアプリを呼び、応答を返します。
クライアントは、トンネルサーバーへ自分から接続します。
手元で試す
以下は 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-oss(GitHub)
- README.ja.md
- docs/production.md(英語)
- docs/security.md(英語)
- docs/how-it-works.md(英語)
※ gridbase 本体は MIT ライセンスですが、同梱の Directus 11.17.4 は BSL 1.1 です。利用者と関連会社の直近12か月の総収益・予算・資金調達額のうち最大のものが500万米ドルを超える場合、本番で使うには、Directus の商用ライセンスが必要です。詳しくは Directus のライセンス本文 を確認してください。