環境管理與 Secrets:staging/production 分流與 wrangler secret 安全實踐 | Cloudflare 完整教學

2026/08/13
環境管理與 Secrets:staging/production 分流與 wrangler secret 安全實踐 | Cloudflare 完整教學

上一篇我們把 wrangler.jsonc 從頭拆到尾,還埋下兩個伏筆:「敏感值不要放 vars」與「用 env 區分 staging 與 production」。這一篇,我們就把這兩件事一次講透。你會學到怎麼用 env.staging / env.production 設定塊搭配 --env 參數把環境分流,弄懂 vars(明文)Secrets(加密) 的本質差異,實作 wrangler secret put / list / delete.dev.vars 本地機密與 Secrets Store,最後示範在 CI/CD 中如何安全注入機密。讀完,你的 staging 與 production 就能各行其道、敏感資訊滴水不漏。

前言

上一篇《wrangler.jsonc 設定》,我們把 Cloudflare Workers 的設定檔徹底拆開,認識了 namemaincompatibility_date 等核心欄位,也走過各種 binding 的宣告語法。過程中我們反覆強調兩件尚未展開的事:敏感值不能塞進 vars,以及env 區分不同環境。這兩件事——環境管理Secrets——正是把一個 Worker 從「本機玩具」推向「正式生產」的關鍵一步。

先給兩句話定義:

  • 環境(Environment) 是同一份 wrangler.jsonc 裡,用 env.<名稱> 設定塊定義的多套部署配置。你可以有一套 staging(測試)、一套 production(正式),它們共用同一份程式碼,卻各自綁不同的資源、走不同的網址、掛不同的變數。部署時用 --env <名稱> 指定要送到哪一套。
  • Secret(機密)加密儲存在 Cloudflare 的敏感值(API Key、密碼、Token),透過 wrangler secret put 上傳。它跟 vars(明文環境變數)最大的不同,是永遠不會出現在你的設定檔或 git 歷史裡

打個比方:如果你的 Worker 是一間連鎖餐廳,那 environments 就像同一套菜單(程式碼)開的兩家分店——一家是「試營運店」(staging),廚房隨便你亂試新菜、打翻鍋子也沒關係;另一家是「旗艦店」(production),客人絡繹不絕、一點閃失都是事故。兩家店用同一本食譜,但各自有獨立的食材庫存、水電帳號、門牌地址。而 Secrets 就是每家店的保險箱鑰匙與供應商密碼——你絕不會把它印在公開的菜單(vars)上,而是鎖進只有店經理拿得到的保險箱(加密儲存),而且兩家店的保險箱各配各的鑰匙,不共用。

理解了這個比方,本篇的所有細節你都能對上號。讀完你會掌握:

  • 多環境設定——env.staging / env.production 設定塊怎麼寫、--env 怎麼用、哪些設定會繼承、哪些不會
  • vars vs secrets——明文與加密的本質差異,以及「什麼該放哪裡」的判準
  • Secrets 全套操作——wrangler secret put / list / delete.dev.vars 本地機密、Secrets Store binding
  • CI/CD 安全注入——在 GitHub Actions 這種非互動環境,如何不外洩地設定與使用機密

核心概念

為什麼需要多環境?

想像你只有一個環境:每次改動都直接部署到跑著真實流量的正式站。這意味著任何一個沒測過的 bug、任何一次手滑,都會立刻打到真實使用者身上。多環境的價值,就是在「你的電腦」和「正式生產」之間,插入一個或多個緩衝地帶:

  • staging(預備/測試環境):一個「長得跟 production 幾乎一樣、但沒有真實使用者」的環境。你在這裡跑整合測試、讓 QA 驗收、給同事 demo。它綁的是測試用的資料庫與 KV,就算資料被玩壞也無所謂。
  • production(正式環境):面向真實使用者、綁真實資源、走正式網域的環境。只有通過 staging 驗證的版本才准進來。

Cloudflare Workers 用 wrangler.jsonc 裡的 env 欄位來實現這件事:同一份程式碼、同一份設定檔,定義多套環境配置。部署時用 --env 決定送到哪一套,底層其實會建立多個獨立的 Worker(名稱通常是 my-workermy-worker-staging)。

vars vs secrets:明文與加密的本質差異

這是本篇最重要的一張表。vars 與 secrets 在程式裡都用 env.XXX 存取,看起來一樣,但底層天差地別:

特性vars(環境變數)Secrets(機密)
儲存位置明文寫在 wrangler.jsonc加密儲存於 Cloudflare
是否進版控會(跟著設定檔 commit)不會(不在設定檔裡)
Dashboard 可見性值可見只看得到名稱,值加密隱藏
適合放什麼非敏感設定API Key、密碼、Token
設定方式直接寫進設定檔wrangler secret put
程式中存取env.XXXenv.XXX(同左)

一句話心法:「外洩會造成損害的,放 secrets;就算公開也無所謂的,放 vars。」

具體對照:

  • vars:ENVIRONMENT(環境名稱)、LOG_LEVEL(log 等級)、API_BASE_URL(公開的 API 網址)、功能開關(feature flag)。
  • Secrets:API_KEYDATABASE_PASSWORDSTRIPE_SECRET_KEYJWT_SIGNING_KEY、第三方服務的 access token。

環境繼承規則:哪些繼承、哪些不繼承

這是新手最常踩的坑,務必記牢。當你在 env.staging 裡定義一套設定時,並不是所有頂層設定都會自動流進來。規則如下:

設定項目是否繼承頂層?
main(入口檔)
compatibility_date / compatibility_flags
build(建置設定)
name(名稱)否(不設會自動追加 -<env>)
vars(環境變數)(必須在環境內重新定義)
kv_namespaces / r2_buckets / d1_databases 等所有 binding
routes(路由)
triggers(Cron)
Secrets(每個環境各自獨立設定)

核心心法:「程式碼相關的設定(入口、相容性、建置)會繼承;但所有『資源與資料』相關的東西(vars、bindings、routes、secrets)都不繼承,必須為每個環境明確定義。」 這個設計是刻意的——它強迫你為 staging 與 production 綁不同的資料庫、不同的金鑰,避免「測試環境不小心寫進正式資料庫」這種災難。

關鍵術語

  • env.<name> 設定塊:wrangler.jsoncenv 物件底下的一個命名區塊(如 env.staging),定義該環境專屬的名稱、變數與綁定。
  • --env <name>:Wrangler 指令的參數,告訴 dev / deploy / secret 等指令「這次針對哪一個環境操作」。
  • .dev.vars:本地開發用的機密檔案,放在專案根目錄,提供 wrangler dev 時的 env.XXX 值,絕不 commit
  • Secrets Store:Cloudflare 帳號層級的集中式機密管理服務,讓多個 Worker / 專案共用同一組機密,以 secrets_store_secrets binding 存取。

實作範例

概念齊了,直接動手。我們用一份「同時定義 staging 與 production」的 wrangler.jsonc 開場,再逐一實作 Secrets 的各種操作。

1. 多環境設定塊:env.staging / env.production

以下是一份標準的多環境設定。留意頂層是「預設環境」,env 底下各自定義 staging 與 production:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-api-worker",
  "main": "src/index.ts",                 // 會被所有環境繼承
  "compatibility_date": "2026-08-07",     // 會被所有環境繼承

  // ── 頂層(預設環境)的 vars ──
  // 注意:這份 vars 不會流進 env.staging / env.production,各環境要各自定義
  "vars": {
    "ENVIRONMENT": "development",
    "LOG_LEVEL": "debug"
  },

  "env": {
    // ── staging:測試環境 ──
    "staging": {
      "name": "my-api-worker-staging",    // 獨立的 Worker 名稱
      "workers_dev": true,                // staging 用 *.workers.dev 就好
      "vars": {
        "ENVIRONMENT": "staging",
        "LOG_LEVEL": "debug"
      },
      // binding 不繼承,必須為 staging 綁「測試用」資源
      "kv_namespaces": [
        { "binding": "CACHE", "id": "<STAGING_KV_ID>" }
      ],
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "my-db-staging",
          "database_id": "<STAGING_D1_ID>"
        }
      ]
    },

    // ── production:正式環境 ──
    "production": {
      "name": "my-api-worker",            // 正式站名稱
      "workers_dev": false,               // 正式站關掉 workers.dev,走自訂網域
      "routes": [
        { "pattern": "api.example.com/*", "zone_name": "example.com" }
      ],
      "vars": {
        "ENVIRONMENT": "production",
        "LOG_LEVEL": "warn"               // 正式站少印一點 log
      },
      // 綁「正式用」資源——刻意跟 staging 分開
      "kv_namespaces": [
        { "binding": "CACHE", "id": "<PRODUCTION_KV_ID>" }
      ],
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "my-db",
          "database_id": "<PRODUCTION_D1_ID>"
        }
      ]
    }
  }
}

有了這份設定,就能用 --env 分流開發與部署:

# 本地開發時載入 staging 環境的設定
npx wrangler dev --env staging

# 部署到 staging(測試)
npx wrangler deploy --env staging

# 通過驗證後,部署到 production(正式)
npx wrangler deploy --env production

不加 --env 時,操作的是頂層的預設環境。實務上團隊常把「預設環境」當成本機/開發用,真正上線的只有 stagingproduction 兩套。

2. 設定 Secrets:wrangler secret put

vars 直接寫在設定檔,但 secrets 不行——它得用指令上傳,才能加密儲存。最安全的方式是互動式輸入:

# 互動式輸入(最安全,推薦)
# 執行後 Wrangler 會提示你貼上值,輸入內容不會顯示、不進 shell 歷史
npx wrangler secret put API_KEY

# 為特定環境設定(關鍵!secrets 不跨環境繼承)
npx wrangler secret put API_KEY --env staging
npx wrangler secret put API_KEY --env production

# 從檔案輸入(適合 PEM 金鑰、憑證這類多行內容)
npx wrangler secret put PRIVATE_KEY < ./certs/private-key.pem

安全鐵律:永遠不要用下面這兩種方式傳值,它們會把機密留在 shell 歷史紀錄或行程列表裡:

# 危險:值會出現在 ~/.zsh_history / ~/.bash_history
echo "my-secret-value" | npx wrangler secret put API_KEY   # 不建議

# 危險:值直接暴露在指令列
npx wrangler secret put API_KEY --text "my-secret-value"   # 不可接受

3. 列出與刪除 Secrets

# 列出所有 secret 名稱(只顯示名稱,不顯示值)
npx wrangler secret list

# 列出特定環境的 secrets——用來排查「這環境到底設過哪些」
npx wrangler secret list --env staging
npx wrangler secret list --env production

# 刪除 secret
npx wrangler secret delete API_KEY
npx wrangler secret delete API_KEY --env staging

secret list 是排查 env.XXXundefined 的第一站——先確認該環境的名單裡到底有沒有這個 key。

4. 批次上傳:wrangler secret bulk

一次要設很多個 secret 時,一個一個 put 太累。可以用 JSON 檔批次上傳(此檔絕不 commit,建議加進 .gitignore):

{
  "API_KEY": "your-secret-value-here",
  "STRIPE_SECRET_KEY": "sk_live_xxxxxxxx",
  "DATABASE_PASSWORD": "super-secret-password"
}
# 批次上傳(單次最多 100 個)
npx wrangler secret bulk secrets.json

# 為特定環境批次上傳
npx wrangler secret bulk secrets.json --env production

5. 本地開發機密:.dev.vars

wrangler secret put 上傳的是線上的機密;本機 wrangler dev 時,Miniflare 讀不到它們。本地開發要靠專案根目錄的 .dev.vars 檔案提供 env.XXX 的值:

# .dev.vars —— 本地開發用,絕不 commit(務必加進 .gitignore)
API_KEY=local-dev-secret-key
DATABASE_URL=postgres://localhost:5432/dev
STRIPE_WEBHOOK_SECRET=whsec_test_xxxxx

你也可以為不同環境準備專屬的 .dev.vars:

# .dev.vars.staging —— 執行 wrangler dev --env staging 時載入
API_KEY=staging-secret-key

在 Worker 程式裡,.dev.vars 的值與線上 secret 的存取方式完全相同,都是 env.XXX——這正是設計的精妙處:同一份程式碼,本機讀 .dev.vars、線上讀加密 secret,無縫切換:

export interface Env {
  API_KEY: string;        // 本機來自 .dev.vars,線上來自 wrangler secret
  ENVIRONMENT: string;    // 來自 vars(明文)
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // 取用機密——本機與線上程式碼一模一樣
    const key = env.API_KEY;
    const isProd = env.ENVIRONMENT === "production";

    // 用機密呼叫外部 API
    const resp = await fetch("https://api.example.com/data", {
      headers: { "Authorization": `Bearer ${key}` },
    });

    // 切記:絕不要 console.log(key) 把機密印出來——會進 wrangler tail 日誌
    return new Response(`env=${env.ENVIRONMENT}, ok=${resp.ok}`);
  },
};

別忘了把機密檔案全部排除在版控之外:

# .gitignore
.dev.vars
.dev.vars.*
secrets.json

6. Secrets Store:帳號層級的集中式機密

前面的 secrets 都是綁在單一 Worker 上的。但如果你有十個 Worker 都要用同一把 OPENAI_API_KEY,難道要各設十次、輪換時各改十次嗎?Secrets Store 就是為此而生——它是 Cloudflare 帳號層級的集中式機密庫,設定一次,多個 Worker 共用。

先用指令在帳號的 Secrets Store 建立機密:

# 在帳號的 Secrets Store 建立一個機密
npx wrangler secrets-store secret create <STORE_ID> \
  --name OPENAI_API_KEY --scopes workers

# 列出 store 裡的機密
npx wrangler secrets-store secret list <STORE_ID>

接著在 wrangler.jsoncsecrets_store_secrets binding 引用它:

{
  "secrets_store_secrets": [
    {
      "binding": "OPENAI_API_KEY",       // → env.OPENAI_API_KEY
      "store_id": "<STORE_ID>",
      "secret_name": "OPENAI_API_KEY"
    }
  ]
}

在程式裡,Secrets Store 的機密要用 .get() 非同步取得(這與一般 secret 直接讀 env.XXX 字串不同):

export interface Env {
  OPENAI_API_KEY: { get(): Promise<string> };
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Secrets Store 的值是非同步取得
    const apiKey = await env.OPENAI_API_KEY.get();
    // ...用 apiKey 呼叫 OpenAI
    return new Response("ok");
  },
};

一般 secret vs Secrets Store 怎麼選? 機密只有這個 Worker 用,就用 wrangler secret put(綁單一 Worker,最單純);機密要跨多個 Worker / 專案共用、想集中輪換,就用 Secrets Store。

7. CI/CD 中安全注入 Secrets

在 GitHub Actions 這種非互動環境,沒有人能手動打字,所以不能用互動式 secret put(會卡住)。這裡有兩層機密要處理,別搞混:

第一層:給 Wrangler 用的部署認證(API Token)。 把 Cloudflare API Token 與 Account ID 存進 GitHub Repository Secrets,在 workflow 以環境變數傳入,wrangler-action 會自動讀取:

# .github/workflows/deploy.yml
name: Deploy Worker
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
      - run: npm ci

      - name: 部署到 production
        uses: cloudflare/wrangler-action@v3
        with:
          # 這兩個是「部署認證」,讓 wrangler 有權限上傳
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: deploy --env production

第二層:設定 Worker 本身要用的 secrets。 如果機密值也存在 GitHub Secrets 裡,用管線把「受保護的環境變數」餵給 wrangler secret put——注意值來自 $MY_API_KEY 這個環境變數,而非寫死的字串,所以不會出現在 workflow 檔案或 log:

      - name: 同步 Worker secrets
        run: |
          echo "$MY_API_KEY" | npx wrangler secret put API_KEY --env production
          echo "$STRIPE_KEY" | npx wrangler secret put STRIPE_SECRET_KEY --env production
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          MY_API_KEY: ${{ secrets.MY_API_KEY }}
          STRIPE_KEY: ${{ secrets.STRIPE_SECRET_KEY }}

搭配漸進式部署時,官方更推薦用版本層級的 wrangler versions secret put,讓機密與特定版本綁在一起(這是下一篇《漸進式部署與版本》的主題)。

常見錯誤與最佳實踐

坑一:把敏感值塞進 vars

這是最嚴重也最常見的錯誤。vars明文寫在設定檔、會 commit 進 git 的,把 API Key、資料庫密碼放進去,等於把它公開貼在 GitHub 上。正確做法:敏感值一律用 wrangler secret put(線上)或 .dev.vars(本地);vars 只放非敏感設定(環境名稱、log 等級、公開網址)。萬一已經誤放並 commit 了,要立刻在來源服務撤銷並輪換那把金鑰,再改用 secret 重新設定——因為只要進過 git 歷史,就當它已經外洩。

坑二:忘了為每個環境各別設 secret。

secrets 不跨環境繼承。你為預設環境跑了 wrangler secret put API_KEY,結果 --env staging 部署上去,env.API_KEY 卻是 undefined正確做法:對每一個會部署的環境都跑一次,例如 wrangler secret put API_KEY --env staging--env production;用 wrangler secret list --env staging 確認名單。

坑三:以為環境設定會全部繼承。

env.production 裡只寫了 routes,卻以為 vars 和 KV binding 會從頂層自動帶下來,結果部署後變數與資料庫全空。正確做法:記牢「程式碼相關設定(main、compatibility)繼承,資源相關設定(vars、bindings、routes、secrets)不繼承」,每個環境的 vars 與 binding 都要明確重寫一遍

坑四:把 .dev.varssecrets.json commit 進版控。

本地機密檔一旦進 git,就跟寫進 vars 一樣糟。正確做法:第一時間把 .dev.vars.dev.vars.*secrets.json 加進 .gitignore,建專案時就設好,別等到寫進機密才想起來。

坑五:staging 綁到 production 的真實資源。

env.staging 裡偷懶,填了正式資料庫的 database_id,結果測試時把真實資料寫壞。正確做法:staging 一定綁獨立的測試資源(測試 KV、測試 D1),讓它就算被玩爆也不影響正式資料。這正是「binding 不繼承」這個設計要保護你的地方。

坑六:把機密 console.log 出來。

為了 debug 印出 console.log(env.API_KEY),結果機密流進 wrangler tail 日誌、甚至 Logpush 的第三方平台。正確做法:絕不記錄機密的值;真要 debug 就只印「有沒有值」(如 console.log("has key:", !!env.API_KEY))。

最佳實踐小結: 記六條——敏感值進 secrets、vars 只放非敏感值;每個環境各別設 secret;謹記資源相關設定不繼承、逐環境明確定義;機密檔一律 .gitignore;staging 綁獨立測試資源;永不 log 機密值。守住這六條,你的多環境與機密管理就穩了。

小結

上一篇《wrangler.jsonc 設定》,我們把設定檔從頭到尾拆開,認識了核心欄位與各種 binding。這一篇,我們兌現了那時埋下的兩個伏筆——多環境管理Secrets:

  • 多環境設定——用 env.staging / env.production 設定塊搭配 --env 參數,把測試與正式分流;每套環境綁獨立的資源與網址,底層是多個獨立的 Worker。
  • 繼承規則——程式碼相關設定(maincompatibility_datebuild)繼承;資源相關設定(vars、bindings、routes、secrets)都不繼承,必須逐環境明確定義。
  • vars vs secrets——vars 明文進版控、放非敏感設定;Secrets 加密儲存、放 API Key 與密碼。判準是「外洩會不會造成損害」。
  • Secrets 操作——wrangler secret put / list / delete / bulk 管理線上機密、.dev.vars 提供本地機密、Secrets Store 做帳號層級的集中共用、CI/CD 用受保護的環境變數安全注入。

你可能注意到,我們在 CI/CD 那節提到了 wrangler versions secret put 這個「版本層級的機密」,還多次帶到「先上 staging 驗證、再上 production」的節奏。這背後其實藏著 Cloudflare 更強大的一套發布機制——版本(Versions)與漸進式部署(Gradual Deployment):上傳新版本卻不立即全量上線,先切 10% 流量觀察、確認無誤再逐步放大,出問題一鍵 rollback。下一篇《漸進式部署與版本》,我們就把這套「零停機、可回滾」的安全發布流程一次講透。

想先查閱官方對環境與 Secrets 的完整說明,可以隨時參考 Cloudflare 官方 Secrets 文件。環境與機密這一課已就緒,我們下一篇《漸進式部署與版本》見。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →