zudo-cloudflare-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

Wrangler 設定

Workers と Pages 用の wrangler.toml 設定

基本構造

wrangler.toml ファイルは Cloudflare プロジェクトを設定する。Pages プロジェクトでは主にバインディングと互換性設定を定義する:

# Cloudflare Pages project configuration
compatibility_date = "2024-12-01"

スタンドアロン Workers ではエントリーポイントとルーティングも含む:

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-12-01"

TOML のテーブルスコープ

wrangler.toml は素の TOML であり、TOML のテーブルのルールがそのまま適用される。[vars][assets][env.preview] のようなテーブルヘッダーを書いた時点で、それに続く裸の key = value 行はすべてそのテーブルに属する。トップレベルには属さない。次の [...] ヘッダーが現れるまでこれは続く。ファイルの末尾にフィールドを追記する際、自分が今どのテーブルの中にいるかを確認しないとこれで足をすくわれる:

[assets]
directory = "./dist"
binding = "ASSETS"

# Intended as a top-level setting, but it lands inside [assets] because
# no new table header appeared above it.
workers_dev = false

wrangler はこの位置ずれを無害なノイズとして黙って受け入れたりしない。各テーブルの既知のフィールドを検証し、属さないものを報告する:

Unexpected fields found in assets field: "workers_dev"

これは警告であってハードエラーではない。ビルド自体は通るが、意図した場所に適用されずフィールドが捨てられるだけだ。workers_dev は一切効かなくなり、長いデプロイログの中では見落としやすいミスになる。

トップレベルのキーは最初のテーブルヘッダーより上にまとめる

namemaincompatibility_datecompatibility_flagsworkers_devpreview_urls はファイルの一番上、どの [...] テーブルよりも前にまとめて書く。トップレベルに置きたいものは、直前に編集していたテーブルだけでなく、すべてのテーブルヘッダーより上に置く必要がある。

バインディング

バインディングはコードを Cloudflare サービスに接続する。

KV ネームスペース

[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456ghi789"

D1 データベース

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "abc123-def456-ghi789"

R2 バケット

[[r2_buckets]]
binding = "FILES"
bucket_name = "my-files"

環境変数

[vars]
API_ENDPOINT = "https://api.example.com"

シークレット(API キー、トークン)には wrangler secret put を使用:

npx wrangler secret put MY_SECRET

シークレットを wrangler.toml に入れてはいけない

[vars] セクションは機密性のない設定専用。シークレットは wrangler secret put または Cloudflare ダッシュボードで設定する。wrangler.toml は git にコミットされる。

必須シークレットの宣言

シークレット自体は wrangler.toml に書かないが、Worker が期待するシークレット名[secrets] テーブルで宣言できる:

[secrets]
required = ["STRIPE_KEY", "SESSION_SECRET"]

これは値を設定するものではない。wrangler が dev 時とデプロイ時に強制するチェックリストだ:

  • wrangler dev: 必須シークレットが欠けていても警告にとどまり、起動は止まらない -- Missing required secrets: STRIPE_KEY. Add them to .dev.vars, .env, or set as environment variables. Worker はそのまま起動し、そのシークレットは単に env に存在しないだけになる。

  • 新規 Worker の初回デプロイ: 必須シークレットが欠けているとハードエラーでデプロイが止まる -- The following required secrets have not been set: STRIPE_KEY. Use wrangler secret put <NAME> to set secrets before deploying.

  • 既存 Worker の再デプロイ: wrangler はまず前バージョンの値を継承するよう API に依頼し、即座には失敗させない。そのシークレットが本当に一度も設定されていなければ API が継承を拒否し、wrangler は同じ「required secrets have not been set」エラーを再度出す -- 失敗自体は変わらず、API のラウンドトリップが一往復増えるだけだ。

--strict-vars は無関係

--strict-vars(デフォルト true)は wrangler types 用のフラグで、[vars] に対して生成する TypeScript の型をリテラル/ユニオン型にするか、広い primitive 型にするかを制御する。[secrets].required とは何の関係もない。名前が似ているだけで、まったく別の機能を指している。

[secrets

[vars] やバインディングと同じく、これも Worker レベルの設定であり名前付き環境からは自動的に見えない。wrangler deploy --env preview[env.preview.secrets] しか見ない -- そのテーブルがなければ、上で説明した必須シークレットの検証はその環境に対して一切実行されず、シークレット未設定のまま preview のデプロイが黙って成功してしまう。

[secrets]
required = ["STRIPE_KEY", "SESSION_SECRET"]

[env.preview.secrets]
required = ["STRIPE_KEY", "SESSION_SECRET"]

バインディングと [vars] に適用される同じ罠については、環境の非継承ルールを参照。

wrangler deploy[vars] を丸ごと置き換える

デフォルトでは、wrangler deploy のたびに**Worker 上に現在ある vars をすべて削除し、wrangler.toml が宣言するものだけに置き換える。**Cloudflare ダッシュボードから応急処置で var を追加していても、次の設定駆動のデプロイでそれは黙って消える -- マージは行われない。

npx wrangler deploy --keep-vars

--keep-vars を使うと削除してから再作成するステップをスキップし、ダッシュボードで設定した vars を wrangler.toml が宣言するものと並存させたまま残す。設定ファイルの外で vars が設定されている可能性がある場合は常にこれを使う。シークレットはどちらの場合でも影響を受けない -- wrangler deploy はこのフラグの有無にかかわらずシークレットを削除しないので、このトラップは [vars] に固有のものだ。

デプロイ前に確認する

現在何がライブになっているか分からない場合は、環境で初めて設定駆動のデプロイを行う前にダッシュボードの Variables & Secrets タブを確認し、--keep-vars が必要かどうかを判断する。

Pages 固有のオプション

pages_build_output_dir = "./dist"

[cache] テーブル

[cache]
enabled = true

[cache] は許可されたフィールドが enabled(boolean)ひとつだけの、正当で有効な wrangler.toml テーブルだ。それ以外のキーを入れると、前述の「Unexpected fields」チェックに同じように引っかかる。このページの他のテーブルより新しくドキュメント化も薄いため、一部エディタの汎用 TOML/JSON スキーマ linter は未知のキーとしてフラグを立てることがある。wrangler 自体は何の問題もなく受け付けているにもかかわらずだ。[cache] の下にエディタが引く赤い波線は、古くなったスキーマのせいだと考えて構わない。エディタの下線ではなく、ファイル全体を検証する wrangler deploy --dry-run で確認すること。

複数バインディングの例

# Cloudflare Pages project configuration
compatibility_date = "2024-12-01"
pages_build_output_dir = "./dist"

[vars]
AUTH0_DOMAIN = "placeholder.us.auth0.com"
AUTH0_CLIENT_ID = "placeholder"

[[d1_databases]]
binding = "DB"
database_name = "my-app"
database_id = "placeholder"

[[r2_buckets]]
binding = "FILES"
bucket_name = "my-app-files"

[[kv_namespaces]]
binding = "CACHE"
id = "placeholder"

プレースホルダー ID

ソース管理ではデータベース ID や KV ネームスペース ID にプレースホルダー値を使用する。実際の ID は環境固有。

名前付き環境とサービスバインディング

実際のプロジェクトでバインディングのセットがひとつだけということはまずない。通常は使い捨てデータを指す preview デプロイ、本番データを指す production デプロイ、そしてその中間の staging デプロイがほしくなる。Wrangler はこれを名前付き環境でモデル化する:[env.preview][env.production][env.staging]。特定の環境は --env でデプロイする:

npx wrangler deploy --env preview
npx wrangler deploy --env production

最大の落とし穴: バインディングと [vars] は継承されない

これはマルチ環境 Worker が壊れる最も一般的な原因だ。トップレベルで宣言したバインディング(D1、R2、KV、AI、サービス)と [vars] は、名前付き環境には引き継がれない。 --env preview でデプロイすると、Worker は [env.preview.*] の下に宣言されたものしか見えず、トップレベルの [vars] やバインディングは黙って捨てられる。

その結果、Worker は D1 接続もなく、KV もなく、API_ENDPOINT が未定義のまま preview で動き、リクエスト時になって初めて失敗することが多い。Wrangler はデプロイ時に警告を出す:

Processing wrangler.toml configuration:
  - "vars" exists at the top level, but not on "env.preview".
    This is not what you probably want, since "vars" is not inherited by environments.
    Please add "vars" to "env.preview".

修正方法は、各環境の中ですべてのバインディングと var を再宣言すること:

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-12-01"

# Top-level [vars] is NOT inherited by named environments below.
# Re-declare it under each [env.*] or the Worker runs without it.
[vars]
API_ENDPOINT = "https://api.example.com"

[env.preview]
# Wrangler does NOT copy the top-level [vars] here. Without this block the
# preview Worker runs with API_ENDPOINT undefined.
[env.preview.vars]
API_ENDPOINT = "https://api-preview.example.com"

[[env.preview.kv_namespaces]]
binding = "CACHE"
id = "placeholder-preview-kv-id"

[env.production]
[env.production.vars]
API_ENDPOINT = "https://api.example.com"

[[env.production.kv_namespaces]]
binding = "CACHE"
id = "placeholder-production-kv-id"

環境ごとにすべて再宣言する -- [assets

バインディング、vars、シークレットに部分的な継承は存在しない。[env.preview] を作るなら、その D1、R2、KV、AI、services、[env.preview.vars]、そして [env.preview.secrets] を自前で列挙しなければならない。バインディングの欠落はデプロイ時にエラーにならず、Worker 内の実行時 undefined として表面化する。[env.preview.secrets] の欠落は違う壊れ方をする -- 必須シークレットの宣言で説明した検証がその環境では一切実行されず、シークレットが未設定のままデプロイが成功してしまう。唯一の例外が [assets](Static Assets)で、以下で扱う。

唯一の例外: [assets

Static Assets は上記のルールを破る。トップレベルで [assets] を宣言し、[env.preview] が自前の [env.preview.assets] を宣言していなければ、env.preview は directory も binding も含めてトップレベルのブロックをそのまま自動的に使う。wrangler は assetsworkers_dev と同じ扱い、つまり再宣言が必要なバインディングではなく通常の継承可能な設定として扱う。

name = "my-site"
main = "./dist/_worker.js"
compatibility_date = "2024-12-01"

[assets]
directory = "./dist"
binding = "ASSETS"

# env.preview declares no [env.preview.assets] block at all,
# so it inherits the top-level [assets] above unchanged.
[env.preview]

継承は環境単位でオール・オア・ナッシングだ。[env.preview.assets] が部分的にでも存在した瞬間、それはマージされるのではなくトップレベルのブロックをまるごと置き換える。だから部分的な上書きであっても、依存しているフィールド(directorybindingnot_found_handlingrun_worker_first)はすべて書く必要がある。

Note

workers_devpreview_urls といったバインディングではない設定は、トップレベルから名前付き環境へ継承される。継承されないルールはバインディング、[vars][secrets] に適用される -- 上で述べた [assets] という唯一の例外を除いて。可読性のために環境ごとに明示的に設定することも依然として一般的。

環境ごとのデータ分離

各環境に独自の database_idbucket_name を持たせることが、staging のトラフィックを本番データに触れさせないための鍵だ。バインディング名(DBBUCKET)は同じままにするので Worker のコードは環境非依存になり、変わるのは背後のリソースだけになる:

name = "sync-server"
main = "src/index.ts"
compatibility_date = "2025-04-01"

[[d1_databases]]
binding = "DB"
database_name = "sync-db"
database_id = "placeholder-prod-d1-id"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "sync-blobs"

[ai]
binding = "AI"

# Staging points the SAME bindings (DB, BUCKET, AI) at SEPARATE resources,
# so staging never reads or writes production data.
[env.staging]
name = "sync-server-staging"

[[env.staging.d1_databases]]
binding = "DB"
database_name = "sync-db-staging"
database_id = "placeholder-staging-d1-id"

[[env.staging.r2_buckets]]
binding = "BUCKET"
bucket_name = "sync-blobs-staging"

[env.staging.ai]
binding = "AI"

参照する前に staging リソースを作成するには:

npx wrangler d1 create sync-db-staging
npx wrangler r2 bucket create sync-blobs-staging

返ってきた database_id[[env.staging.d1_databases]] に貼り付ける。

サービスバインディング: ある Worker が別の Worker を呼ぶ

サービスバインディングを使うと、ある Worker が別の Worker を Cloudflare の内部エッジ経由で直接呼び出せる。呼び出しが Cloudflare のネットワークを出ないため、公開インターネットの経由も、DNS ルックアップも、CORS もない。論理的な名前をデプロイ済み Worker の名前にバインドする:

[env.preview]

# Service binding: this preview Worker → the "image-resizer-preview" Worker.
# The caller reaches it via env.IMAGE_RESIZER without a public request.
[[env.preview.services]]
binding = "IMAGE_RESIZER"
service = "image-resizer-preview"

[[env.preview.services]]
binding = "NOTIFY_WORKER"
service = "notify-worker-preview"

[env.production]

# Same logical bindings, wired to the production target Workers.
[[env.production.services]]
binding = "IMAGE_RESIZER"
service = "image-resizer-prod"

[[env.production.services]]
binding = "NOTIFY_WORKER"
service = "notify-worker-prod"

バインディング名(IMAGE_RESIZER)は環境をまたいで一定なのに対し、service のターゲットは -preview-prod で切り替わる点に注目。コードからは fetch のように呼ぶが、リクエストは内部でディスパッチされる:

// env.IMAGE_RESIZER is the service binding; this never hits the public internet.
const res = await env.IMAGE_RESIZER.fetch("https://internal/resize", {
  method: "POST",
  body: imageBytes,
});

本番環境のみのカスタムドメインルート

apex と www のドメインは custom_domain = true で production 環境にのみ割り当てる。こうすれば preview デプロイは生成された *.workers.dev の URL に留まり、本物のドメインを配信することはない:

[env.production]

# Custom-domain routes — production environment only.
[[env.production.routes]]
pattern = "example.com"
custom_domain = true

[[env.production.routes]]
pattern = "www.example.com"
custom_domain = true

custom_domain = true は、既存のゾーンのルートパターンに一致させるのではなく、そのホスト名の DNS レコードと TLS 証明書を Cloudflare が自動で作成・管理するよう指示する。

名前付き環境はトップレベルの routes を継承する — デプロイするとドメインを奪う

routes継承されるキーである。カスタムドメインをトップレベルに置き、名前付き環境で上書きしなければ、その環境は production のホスト名を継承する。wrangler 自身がはっきり警告する:

The "env.preview" environment inherits the top-level routes configuration, which includes the custom domain(s): example.com. Deploying this environment will reassign these custom domains away from the top-level Worker. Add "routes": [] to "env.preview" to prevent inheritance.

修正は production 以外の各環境に 1 行の上書きを足すだけ:

[env.preview]
routes = []

これが表面化しにくい理由。 wrangler versions upload で組まれた preview パイプラインはトリガーを適用しないため、設定ミスは休眠したままで、どのデプロイも正常に見える。後からそのパイプラインを wrangler deploy --env preview に変えると — それ自体はまっとうなリファクタリングだが — 次の実行で preview の Worker が production のホスト名を黙って奪う。

wrangler deploy --dry-run --env <name>routes: [] と報告されることを確認すること。デプロイが緑だったことから推測してはいけない。

Revision History

作成更新