🌐

Secrets Storeを使ってCloudflare Workersで環境変数を使い分けるためのwrangler.jsoncの設定

に公開

背景

個人開発で React RouterのframeworkモードをCloudflare Workersにデプロイしているのですが、最近 Cloudflare で Secrets StoreというAWSやGoogle CloudのSecret Manager相当のものがWorkersでbeta版として使えるようになったとのことで使ってみました。

ちなみに https://github.com/remix-run/react-router-templates のCloudflareテンプレをベースに作っています。

その時に環境ごとにSecretsを切り替えるようにしようとした際のポイントの覚え書きがてら誰かの参考になればと書きました。

Secrets Storeへの変数の登録

ダッシュボードから登録しても良いですし、下記のコマンドでwranglerを使った登録も可能です。

npx wrangler secrets-store secret create <STORE_ID> --name MY_SECRET_NAME --scopes workers --remote

重要ポイント

環境指定方法

以前業務でWorkersの環境変数を設定する際は

# wrangler.toml

name = "my-worker"
compatibility_date = "2025-04-03"
main = "./src/index.ts"

vars = { MY_VAR = "Top-level var" }

[env.staging]
vars = { MY_VAR = "Staging var" }

[env.production]
vars = { MY_VAR = "Production var" }

という形式で書き、

wrangler deploy --env [環境名]

という形式で環境ごとにデプロイできたはずだったので、今回はjsoncではあるが同じ形でやってみるも動かない。

調べてみると、Viteを使ってbuildしてdeployする場合はそれではダメらしい。

buildする際に CLOUDFLARE_ENVで環境を指定すると、build時に環境用のwrangler.jsonが生成されてそれを使ってデプロイしてくれるようでした。

※詳しくは下記を見てください
https://developers.cloudflare.com/workers/vite-plugin/reference/cloudflare-environments/

なので、デプロイコマンドを下記に変更し対応しました。

~~~
"deploy:staging": "CLOUDFLARE_ENV=staging pnpm build && wrangler deploy",
"deploy:production": "CLOUDFLARE_ENV=production pnpm build && wrangler deploy",
~~~

binding と secret_name

使い慣れている人は一瞬でわかるのでしょうが、どっちが実際にコードの中で使われるものか迷いました。

正解は binding です。
secret_nameは Secrets Store で設定したSecretの名前です。

そう書くと当たり前ですね。

なので、

{
  "binding": "コードの中で使う環境変数名"
  "store_id": "Secret Storeページで取得できるストア ID",
  "secret_name": "Secret Storeで設定したシークレットの名前",
},

をかけばOKです。

wrangler.jsoncの最終系

上記のポイントを押さえれば環境ごとの設定方法は、下記を見れば一目瞭然だと思います。

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "[your-app-name]",
  "compatibility_flags": ["nodejs_compat"],
  "compatibility_date": "2025-02-24",
  "main": "./workers/app.ts",
  "assets": {
    "directory": "./build/client",
  },
  "vars": {
    "YOUR_VARS": "MY_VAR",
  },
  "observability": {
    "logs": {
      "enabled": true,
    },
  },
  "env": {
    "staging": {
      "routes": [{ "pattern": "your-staging-domain.com", "custom_domain": true }],
      "secrets_store_secrets": [
        {
          "binding": "DATABASE_URL",
          "store_id": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "secret_name": "STAGING_DATABASE_URL",
        },
        {
          "binding": "CLERK_PUBLISHABLE_KEY",
          "store_id": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "secret_name": "STAGING_CLERK_PUBLISHABLE_KEY",
        },
        {
          "binding": "CLERK_SECRET_KEY",
          "store_id": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "secret_name": "STAGING_CLERK_SECRET_KEY",
        },
      ],
      "vars": {
        "ENV": "staging",
      },
    },
    "production": {
      "routes": [{ "pattern": "your-production-domain", "custom_domain": true }],
      "secrets_store_secrets": [
        {
          "binding": "DATABASE_URL",
          "store_id": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "secret_name": "PRODUCTION_DATABASE_URL",
        },
        {
          "binding": "CLERK_PUBLISHABLE_KEY",
          "store_id": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "secret_name": "PRODUCTION_CLERK_PUBLISHABLE_KEY",
        },
        {
          "binding": "CLERK_SECRET_KEY",
          "store_id": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "secret_name": "PRODUCTION_CLERK_SECRET_KEY",
        },
      ],
      "vars": {
        "ENV": "production",
      },
    },
  },
}

データの取得の仕方

設定した環境変数はWorkersのenvにbindされている変数に対してgetを呼び出すことで取得できます(ドキュメント: https://developers.cloudflare.com/secrets-store/integrations/workers/#3-access-the-secret-on-the-env-object)

ただ、執筆時点で最新版である wrangler 4.13.2 を使って wrangler types を実行しても取得後の型しか取得できないので型アサーションする必要がありました。

下記は React Router 内にあるWorkers用の関数ですが、単体で使う場合も同様に動くはずです。

const requestHandler = createRequestHandler(
  () => import('virtual:react-router/server-build'),
  import.meta.env.MODE
);

export default {
  async fetch(request, env, ctx) {
    const envVariables =
      import.meta.env.MODE === 'production'
        ? {
            ...env,
            DATABASE_URL: await (
              env.DATABASE_URL as unknown as { get(): Promise<string> }
            ).get(),
            CLERK_PUBLISHABLE_KEY: await (
              env.CLERK_PUBLISHABLE_KEY as unknown as { get(): Promise<string> }
            ).get(),
            CLERK_SECRET_KEY: await (
              env.CLERK_SECRET_KEY as unknown as { get(): Promise<string> }
            ).get(),
          }
        : env;
    const loadContext = getLoadContext({
      request,
      context: {
        cloudflare: {
          env: envVariables,
          ctx,
        },
      },
    });
    return requestHandler(request, loadContext);
  },
} satisfies ExportedHandler<CloudflareEnvironment>;

最後に

参考になったらXフォローしていただけると嬉しいです。

https://x.com/sidekix_dev

エンジニアの方に利用していただけるようなプロダクトを個人で開発中なので、リリースする際はXで告知します。

また、所属している会社でエンジニアを探しておりますので下記の内容に興味がある方はXでDMをください。

概要
グローバルに展開するECプラットフォームを作っている会社です。
実際に海外売上も立っており、売上は伸び続けています。
toCの側面もtoBの側面もありますので、どちら方面の開発にも関わっていけます。もちろん一方に集中していただくことも可能です。

また、まだReact Router v7にバージョンを上げていないですが、Remixを本番環境で使っている珍しい会社かと思います。

技術スタック的には主に下記の両方が書けるもしくは一方が書ける方を探しています。

  • Ruby on Rails
  • React.js

GraphQLやShopify APIの経験がおありの方、大歓迎です。

React Nativeが書けるネイティブアプリエンジニアも募集中です。

DMお待ちしております!

参考

Discussion