環境管理與 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 的設定檔徹底拆開,認識了 name、main、compatibility_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-worker、my-worker-staging)。
vars vs secrets:明文與加密的本質差異
這是本篇最重要的一張表。vars 與 secrets 在程式裡都用 env.XXX 存取,看起來一樣,但底層天差地別:
| 特性 | vars(環境變數) | Secrets(機密) |
|---|---|---|
| 儲存位置 | 明文寫在 wrangler.jsonc | 加密儲存於 Cloudflare |
| 是否進版控 | 會(跟著設定檔 commit) | 不會(不在設定檔裡) |
| Dashboard 可見性 | 值可見 | 只看得到名稱,值加密隱藏 |
| 適合放什麼 | 非敏感設定 | API Key、密碼、Token |
| 設定方式 | 直接寫進設定檔 | wrangler secret put |
| 程式中存取 | env.XXX | env.XXX(同左) |
一句話心法:「外洩會造成損害的,放 secrets;就算公開也無所謂的,放 vars。」
具體對照:
- 放
vars:ENVIRONMENT(環境名稱)、LOG_LEVEL(log 等級)、API_BASE_URL(公開的 API 網址)、功能開關(feature flag)。 - 放 Secrets:
API_KEY、DATABASE_PASSWORD、STRIPE_SECRET_KEY、JWT_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.jsonc裡env物件底下的一個命名區塊(如env.staging),定義該環境專屬的名稱、變數與綁定。--env <name>:Wrangler 指令的參數,告訴dev/deploy/secret等指令「這次針對哪一個環境操作」。.dev.vars:本地開發用的機密檔案,放在專案根目錄,提供wrangler dev時的env.XXX值,絕不 commit。- Secrets Store:Cloudflare 帳號層級的集中式機密管理服務,讓多個 Worker / 專案共用同一組機密,以
secrets_store_secretsbinding 存取。
實作範例
概念齊了,直接動手。我們用一份「同時定義 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 時,操作的是頂層的預設環境。實務上團隊常把「預設環境」當成本機/開發用,真正上線的只有 staging 與 production 兩套。
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.XXX 為 undefined 的第一站——先確認該環境的名單裡到底有沒有這個 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.jsonc 用 secrets_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.vars 或 secrets.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。 - 繼承規則——程式碼相關設定(
main、compatibility_date、build)繼承;資源相關設定(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 文件。環境與機密這一課已就緒,我們下一篇《漸進式部署與版本》見。