Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

トンネル

最終更新 Markdown で表示Agent セットアップ

sandbox.tunnels 名前空間は、Sandbox 内で動いているサービスを Cloudflare Tunnel 経由でインターネットに公開します。SDK はコンテナ内で cloudflared を動かし、Cloudflare のエッジへの永続的な QUIC 接続を開きます。

次の 2 種類があります。

  • クイックトンネルsandbox.tunnels.get(port)) — 設定は不要です。Cloudflare が新しい cloudflared プロセスごとにランダムな *.trycloudflare.com ホスト名を割り当てます。Cloudflare アカウント、API トークン、DNS レコード、カスタムドメインは不要です。コンテナを再起動するたびに URL は変わります。
  • 名前付きトンネルsandbox.tunnels.get(port, { name })) — 管理下のゾーン上の安定したホスト名 <name>.<your-zone> に紐づけます。ホスト名はコンテナ再起動後も残り、同じ name を要求する Sandbox 間で共有されます。Cloudflare API トークン、アカウント、ゾーンが必要です。

要件

どちらのトンネルも次が必要です。

  • RPC トランスポート。 HTTP / Websocket トランスポートで sandbox.tunnels を呼ぶと "RPC transport required" がスローされます。トランスポートの設定 を参照してください。

名前付きトンネルには、さらに Cloudflare API トークン、アカウント、ゾーンが必要です。名前付きトンネル: 前提条件 を参照してください。

メソッド

tunnels.get()

port のトンネルレコードを返します。まだ動いていなければ、SDK はコンテナ内で新しい cloudflared プロセスを起動します。このメソッドは冪等です。同じ (port, options) で繰り返すと、同じレコードを返します。

const tunnel = await sandbox.tunnels.get(
  port: number,
  options?: { name?: string }
): Promise<TunnelInfo>

パラメーター:

  • port — Sandbox 内で公開するポート番号です(1024–65535。予約済みポートは除きます)。トンネル先のサービスは、コンテナ内の 0.0.0.0:<port> ですでに待ち受けている必要があります。
  • options.name (任意) — 単一の DNS ラベルです(小文字、数字、内部のハイフン。1–63 文字。ドットなし)。設定すると、<name>.<your-zone> に紐づく 名前付きトンネル をプロビジョニングします。省略すると、クイックトンネルをプロビジョニングします。

戻り値: Promise<TunnelInfo> — トンネルレコードです。TunnelInfo を参照してください。

すでにトンネルがあるポートに対して、異なる optionsget(port) を呼ぶとスローされます。先に destroy(port) を呼んでください。

import { getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
	async fetch(request, env) {
		const sandbox = getSandbox(env.Sandbox, "my-sandbox");

		await sandbox.startProcess("python -m http.server 8080");

		const tunnel = await sandbox.tunnels.get(8080);
		console.log(tunnel.url);
		// → https://random-words-here.trycloudflare.com

		// Repeated calls for the same port return the same record.
		const same = await sandbox.tunnels.get(8080);
		console.log(same.url === tunnel.url); // true

		return Response.json({ url: tunnel.url });
	},
};
import { getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sandbox = getSandbox(env.Sandbox, "my-sandbox");

    await sandbox.startProcess("python -m http.server 8080");

    const tunnel = await sandbox.tunnels.get(8080);
    console.log(tunnel.url);
    // → https://random-words-here.trycloudflare.com

    // Repeated calls for the same port return the same record.
    const same = await sandbox.tunnels.get(8080);
    console.log(same.url === tunnel.url); // true

    return Response.json({ url: tunnel.url });

},
};

tunnels.list()

この Sandbox で現在追跡しているトンネルをすべて返します。

const tunnels = await sandbox.tunnels.list(): Promise<TunnelInfo[]>

戻り値: Promise<TunnelInfo[]>TunnelInfo レコードの配列です。アクティブなトンネルがないときは空です。

const tunnels = await sandbox.tunnels.list();

for (const tunnel of tunnels) {
	console.log(`port ${tunnel.port} → ${tunnel.url}`);
}
const tunnels = await sandbox.tunnels.list();

for (const tunnel of tunnels) {
console.log(`port ${tunnel.port} → ${tunnel.url}`);
}

tunnels.destroy()

トンネルを破棄します。ポート番号、または get() が返した TunnelInfo レコードのいずれかを受け付けます。冪等です。未知のポートを破棄しても成功として解決します。

await sandbox.tunnels.destroy(portOrInfo: number | TunnelInfo): Promise<void>

パラメーター:

  • portOrInfo — ポート番号、または get() が返した TunnelInfo レコードのいずれかです。
const tunnel = await sandbox.tunnels.get(8080);

// Tear down by port number...
await sandbox.tunnels.destroy(8080);

// ...or by the record.
await sandbox.tunnels.destroy(tunnel);
const tunnel = await sandbox.tunnels.get(8080);

// Tear down by port number...
await sandbox.tunnels.destroy(8080);

// ...or by the record.
await sandbox.tunnels.destroy(tunnel);

TunnelInfo

クイックトンネルには name がありません。名前付きトンネルは、options.name で渡したラベルを持ちます。

フィールド 説明
id string トンネル識別子です。クイックトンネルは quick-<random>、名前付きトンネルは Cloudflare Tunnel の UUID です。
port number トンネルがプロキシする Sandbox 内のポート番号です。
url string 公開 URL です。クイックは https://<random>.trycloudflare.com、名前付きは https://<name>.<your-zone> です。
hostname string url のホスト名部分です。
createdAt string トンネル作成時の ISO-8601 タイムスタンプです。
name string 名前付きトンネルのみ。 options.name で渡したラベルです。クイックトンネルにはありません。
type TunnelInfo = QuickTunnelInfo | NamedTunnelInfo;

interface QuickTunnelInfo {
  id: string;
  port: number;
  url: string;
  hostname: string;
  createdAt: string;
  name?: never;
}

interface NamedTunnelInfo {
  id: string;
  port: number;
  url: string;
  hostname: string;
  createdAt: string;
  name: string;
}

名前付きトンネル

名前付きトンネルは、ユーザーが管理するホスト名 <name>.<your-zone> を、マネージドの Cloudflare Tunnel とゾーン上のプロキシ済み CNAME レコードで支えます。クイックトンネルと違い、URL は コンテナ再起動をまたいで安定 し、同じ nameget(port, { name }) を呼ぶ Sandbox 間で共有 されます。

クイックトンネルとの違い

項目 クイックトンネル 名前付きトンネル
ホスト名 Cloudflare が割り当てるランダムな *.trycloudflare.com 自分で選ぶ <name>.<your-zone>
安定性 コンテナを再起動するたびに変わる 安定。再起動と Sandbox のライフサイクルをまたいで残ります
Cloudflare アカウント 不要 必要(API トークン + ゾーン)
Cloudflare 側のリソース なし マネージドの Cloudflare Tunnel + プロキシ済み DNS CNAME
稼働保証 なし(デバッグ用) ゾーンの標準 Cloudflare SLA が適用されます
TLS 証明書 Cloudflare 所有のワイルドカード <name>.<your-zone> の Universal SSL(単一 DNS ラベルのみ)
Server-Sent Events 非対応(エッジが text/event-stream をバッファします) 対応

前提条件

名前付きトンネルをプロビジョニングするには、次が必要です。

  1. ゾーン(Cloudflare DNS で管理するドメイン)を持つ Cloudflare アカウント
  2. 適切なスコープを持つ Cloudflare API トークン
  3. アカウント IDゾーン ID — トークンのスコープがそれぞれちょうど 1 つなら、SDK がトークンから両方を推定できます。

API トークンを作成する

My Profile > API Tokens > Create Token > Custom token から、次の権限でトークンを作成します。

スコープ 用途
Account · Cloudflare Tunnel · Edit トンネルの作成、参照、削除。
Zone · DNS · Edit <name>.<your-zone> のプロキシ済み CNAME の作成または更新と削除。
Zone · Zone · Read ゾーン名を参照し、<name>.<your-zone> を導出する。
Account · Account Settings · Read (任意) 明示的に設定しないとき、SDK がトークンからアカウント ID を推定できるようにします。

Account Resources では、トンネルを所有するアカウントにトークンをスコープします。Zone Resources では、紐づけたい特定のゾーンにスコープします。

User API TokensAccount API Tokens(シークレットの接頭辞は cfat_)の両方に対応しています。SDK はトークンの種類を検出し、適切なイントロスペクションエンドポイントを使います。

REST API でトークンを作成する

ダッシュボードを使わずにトークンを作成できます。権限グループ ID は安定しています。次のスニペットはプレースホルダーを使っています。現在の ID は GET /user/tokens/permission_groups から取得してください。

curl -X POST "https://api.cloudflare.com/client/v4/user/tokens" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "sandbox-named-tunnels",
    "policies": [
      {
        "effect": "allow",
        "resources": { "com.cloudflare.api.account.<ACCOUNT_ID>": "*" },
        "permission_groups": [{ "id": "<TUNNEL_EDIT_GROUP_ID>" }]
      },
      {
        "effect": "allow",
        "resources": { "com.cloudflare.api.account.zone.<ZONE_ID>": "*" },
        "permission_groups": [
          { "id": "<DNS_EDIT_GROUP_ID>" },
          { "id": "<ZONE_READ_GROUP_ID>" }
        ]
      }
    ]
  }'

トークンと ID を Worker にバインドする

SDK は Worker 環境から CLOUDFLARE_API_TOKEN を読み、トークンからアカウント ID とゾーン ID の推定を試みます。トークンが複数のアカウントまたはゾーンに関連付いていて SDK が 1 つに決められない場合は、CLOUDFLARE_ACCOUNT_IDCLOUDFLARE_ZONE_ID を明示的に設定する必要があります。

変数 必須? 備考
CLOUDFLARE_API_TOKEN はい wrangler secret put でシークレットとして保存します。
CLOUDFLARE_ACCOUNT_ID トークンが複数アカウントを見る場合のみ それ以外はトークンから推定します。
CLOUDFLARE_ZONE_ID トークンが複数ゾーンを見る場合のみ それ以外はトークンから推定します。
npx wrangler secret put CLOUDFLARE_API_TOKEN

ローカル開発では、変数を .dev.vars(gitignore 済み)に置きます。本番では、シークレットではない ID(必要なとき)を Wrangler 設定の vars に置きます。

{
  "vars": {
    "CLOUDFLARE_ACCOUNT_ID": "<account-id>",
    "CLOUDFLARE_ZONE_ID": "<zone-id>"
  }
}

推定に失敗すると、SDK は設定すべき変数名を明示した分かりやすいエラーをスローします。

import { getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
	async fetch(request, env) {
		const sandbox = getSandbox(env.Sandbox, "my-sandbox");

		// Reuse an existing app process across container restarts, or start it.
		let proc = await sandbox.getProcess("app");
		if (!proc) {
			try {
				proc = await sandbox.startProcess("python -m http.server 8080", {
					processId: "app",
				});
			} catch (err) {
				if (err?.code !== "PROCESS_ALREADY_EXISTS") throw err;
				proc = await sandbox.getProcess("app");
			}
		}

		// Provision (or reuse) https://app.example.com pointing at port 8080.
		const tunnel = await sandbox.tunnels.get(8080, { name: "app" });
		console.log(tunnel.url); // → https://app.example.com

		return Response.json({ url: tunnel.url });
	},
};
import { getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sandbox = getSandbox(env.Sandbox, "my-sandbox");

    // Reuse an existing app process across container restarts, or start it.
    let proc = await sandbox.getProcess('app');
    if (!proc) {
      try {
        proc = await sandbox.startProcess('python -m http.server 8080', { processId: 'app' });
      } catch (err) {
        if ((err as { code?: string })?.code !== 'PROCESS_ALREADY_EXISTS') throw err;
        proc = await sandbox.getProcess('app');
      }
    }

    // Provision (or reuse) https://app.example.com pointing at port 8080.
    const tunnel = await sandbox.tunnels.get(8080, { name: "app" });
    console.log(tunnel.url); // → https://app.example.com

    return Response.json({ url: tunnel.url });
  },
};

ライフサイクル

名前付きトンネルは、プロビジョニングしたコンテナより長く残るように設計されています。

  1. sandbox.tunnels.get(port, { name })最初の呼び出し:
    • 設定したゾーン ID から <name>.<your-zone> を解決します。
    • Sandbox ID でタグ付けした Cloudflare Tunnel リソース sandbox-<sandbox-id>-<name> を作成します。
    • <name>.<your-zone> から <tunnel-id>.cfargotunnel.com へのプロキシ済み CNAME を作成または更新します。
    • トンネルのトークンを使って、コンテナ内で cloudflared を起動します。
  2. 同じ (port, name) での 以降の呼び出し は、Cloudflare に問い合わせず、キャッシュしたレコードを返します。
  3. コンテナ再起動(Durable Object の退避、デプロイ、クラッシュ):
    • cloudflared はコンテナとともに終了しますが、Cloudflare Tunnel と DNS レコードは残ります。
    • 次の get(port, { name }) で、SDK は Cloudflare API 経由でタグ付きトンネルを再発見し、cloudflared を再起動します。ホスト名は変わりません。
  4. sandbox.tunnels.destroy(port) による 明示的な破棄:
    • コンテナ内の cloudflared を停止します。
    • Cloudflare Tunnel リソースを削除します。
    • プロキシ済み CNAME レコードを削除します。
  5. sandbox.destroy() による Sandbox の破棄 は、コンテナを止める前に、その Sandbox がプロビジョニングしたトンネル(Cloudflare 側のリソースを含む)をすべて破棄します。

destroy() が Cloudflare API に届かない場合(たとえば get()destroy() の間にトークンが取り消された場合)、SDK は孤立した tunnelIddnsRecordId を警告としてログに出し、ダッシュボードから手動で片付けられるようにします。

Cloudflare のリソースとタグ付け

名前付きトンネルは、Sandbox コンテナの外、自分の Cloudflare アカウント上 にリソースを作ります。Durable Object ストレージには保存されず、Sandbox のクォータにも計上されません。ただし Cloudflare ダッシュボードに表示され、アカウントのトンネルと DNS のクォータは消費します。

(sandbox, name) の組に対して、SDK は次を作成します。

リソース 名前 / 場所 識別子
Cloudflare Tunnel Networking > Tunnels sandbox-<sandbox-id>-<name>
プロキシ済み DNS レコード 自分のゾーン、DNS > Records CNAME <name>.<zone> → <tunnel-id>.cfargotunnel.com

どちらのリソースもタグ付けされているため、ダッシュボードまたは API から監査、照会、一括削除できます。

  • トンネルのメタデータ: { sandboxId, createdBy: 'sandbox-sdk', name, port }
  • DNS レコードのコメント: sandbox-<sandbox-id>
  • リソースタグ (Enterprise プランのみ): sandboxId:<sandbox-id>

Enterprise 以外のプランでは、Cloudflare はリソースタグを拒否します。SDK はこれを検出し、タグなしでリクエストを再試行します。DNS コメントとトンネルメタデータは引き続き付くため、リソースを Sandbox までたどることは常にできます。

指定したアカウントで SDK が作成したトンネルをすべて一覧するには:

curl "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/cfd_tunnel?name=sandbox-" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

制限

どちらのトンネルにも共通:

  • WARP / Zero Trust の egress。 ローカルマシンで Cloudflare WARP または別の Zero Trust egress ポリシーが動いていると、api.trycloudflare.com と cloudflared エッジへの送信がブロックされることがあります。その場合、tunnels.get() はエッジのハンドシェイクで止まり、最終的にタイムアウトします。WARP を無効にするか、これらの宛先に egress の例外を追加してください。
  • 短い DNS のウォームアップ。 作成直後の URL への最初のリクエストは、get() が解決したあとでも、DNS が伝播するまで数秒かかることがあります。

クイックトンネルのみ:

  • URL はコンテナ再起動後に残りません。 Cloudflare は cloudflared の起動ハンドシェイク中にホスト名を割り当てるため、再起動のたびに新しい URL になります。SDK はコンテナ起動時にトンネルキャッシュを消すため、次の tunnels.get(port) は新しいレコードを返します。安定したホスト名には 名前付きトンネル を使ってください。
  • 稼働保証はありません。 Cloudflare は trycloudflare.com をデバッグ用として位置づけており、本番向けではありません。
  • Server-Sent Events は使えません。 trycloudflare.com のエッジは text/event-stream レスポンスをバッファするため、SSE イベントはクライアントに届きません。WebSocket は通常どおり動きます。サービスが SSE をストリーミングする場合は 名前付きトンネル を使ってください。

名前付きトンネルのみ:

  • 単一の DNS ラベル。 name にドットを含められません。Universal SSL がカバーするのは <name>.<your-zone> だけです。
  • ゾーンのクォータに計上されます。 名前付きトンネルは、アカウント上に Cloudflare Tunnel と DNS レコードを 1 つずつ作ります。Cloudflare Tunnel の制限 を参照してください。
  • クリーンアップには API トークンが必要です。 トークン取り消し後に destroy() が走ると、Cloudflare 側のリソースは孤立します。SDK は孤立 ID をログに出すので、手動で削除できます。

関連リソース

役に立ちましたか?