免費

讓 Claude 做生產部署

部署出錯沒人告訴你。用獨立 credentials、EDITOR 腳本、readback 校驗、env 自動切換四道護欄,讓 Claude 能參與但不失控。


部署和寫程式最大的不同是:一次性、高風險、回滾麻煩。寫錯一行程式,測試跑一次就發現。寫錯一行 credentials,你要到第一次真實支付失敗才發現——這時候使用者已經付了錢、錢沒進你帳戶、日誌裡全是 400。

最近用 Claude 把 how2claude 從本地開發推上正式環境,要配 Stripe live 帳號、x402 mainnet 錢包、Google OAuth、Kamal secrets。Claude 不知道哪些值是測試環境、哪些是真的,不知道 sk_live_sk_test_ 差一個字母就是天壤之別。護欄得自己搭。

護欄 #1:dev 和 prod 用兩個獨立的 credentials 檔

Rails 預設的 config/credentials.yml.enc,解密金鑰 config/master.key。用這個預設你就輸了。

如果你把 sk_live_xxx 塞進這個檔,然後本地跑測試——測試程式就在用正式 Stripe。跑一次就扣一次錢。

換成兩個:
- config/credentials.yml.enc + config/master.key:dev/test 用,塞 sk_test_xxx
- config/credentials/production.yml.enc + config/credentials/production.key:正式用,塞 sk_live_xxx

.gitignore 裡兩個 .key 都忽略,兩個 .enc 都 commit。production.key 單獨放在機器上。

然後 Kamal secrets 要指對檔:

 # .kamal/secrets
-RAILS_MASTER_KEY=$(cat config/master.key)
+RAILS_MASTER_KEY=$(cat config/credentials/production.key)

容器裡的 RAILS_MASTER_KEY 指向正式金鑰,解出來就是正式 credentials。Claude 寫這行的時候我特意盯著——預設產生的 Kamal 模板是 config/master.key(dev 的),完全靜默地把 dev Stripe key 部署到正式環境。

護欄 #2:用 EDITOR 腳本塞值,不手動複製貼上

bin/rails credentials:edit --environment production 要開一個互動式編輯器。Claude 驅動不了互動式編輯器。你也不想手動複製十幾個密鑰(一個打錯全家福崩)。

用這個 pattern:

EDITOR="ruby script/set_prod_webhook_secret.rb" \
  bin/rails credentials:edit --environment production
rm script/set_prod_webhook_secret.rb

script/set_prod_webhook_secret.rb 長這樣:

# ARGV[0] 是解密後的暫存 YAML 檔路徑
file = ARGV[0]
require "yaml"
data = YAML.load_file(file) || {}
data["stripe"] ||= {}
data["stripe"]["webhook_secret"] = "whsec_GHWObNAKFh2HPOlJpbGmlYfIiKz1C8EY"
File.write(file, data.to_yaml)

Rails 把解密後的 YAML 寫到暫存檔,把檔案路徑當參數叫 "EDITOR",退出後重新加密。我們的「EDITOR」實際上是個 Ruby 腳本,精確修改一個鍵,存回。

好處:
- 精確:只動 stripe.webhook_secret 這一個欄位,不動其他。
- 冪等:腳本跑兩次結果一樣。
- 可稽核:腳本本身就是異動的 diff。Claude 寫完腳本我看一眼就知道它要改什麼。
- 刪除即忘rm 之後磁碟上沒有明文 credential 殘留,shell history 裡也沒有 whsec_... 的複製貼上痕跡。

每個 credential 都寫一個腳本:set_stripe_live_key.rbset_webhook_secret.rbset_price_ids.rbset_wallet_address.rb。寫完立刻 rm。

護欄 #3:寫完用 Rails runner 回讀校驗

寫進去之後,不要相信它寫對了。讀回來

bin/rails runner -e production "
c = Rails.application.credentials
puts 'sk_live set: ' + c.dig(:stripe, :secret_key).to_s.start_with?('sk_live_').to_s
puts 'webhook_secret set: ' + c.dig(:stripe, :webhook_secret).to_s.start_with?('whsec_').to_s
puts 'wallet prefix: ' + c.dig(:x402, :wallet_address).to_s[0..5]
puts 'wallet len: ' + c.dig(:x402, :wallet_address).to_s.length.to_s
"

關鍵是前綴長度校驗,不是光印出來看。

  • Stripe live secret key 永遠是 sk_live_ 開頭。如果讀出來是 sk_test_ 開頭,Claude 把 test key 塞進了 prod 檔——這個 bug 在第一次真實支付前你不會發現。
  • Webhook secret 永遠是 whsec_ 開頭。格式對不對一眼看出來。
  • EVM 錢包位址永遠是 42 字元 0x...。長度不對就說明混進了別的字元。

前綴校驗加上去,手誤、漏欄位、混環境這類錯誤基本都能擋住。

護欄 #4:讓執行時按 env 自動切換,不要 deploy 時手動切

容易想到「在 deploy 前把 network 改成 mainnet」——這種切換靠人記憶,遲早出事。

x402 initializer 裡直接寫死規則:

# config/initializers/x402.rb
X402.configure do |c|
  c.wallet_address = Rails.application.credentials.dig(:x402, :wallet_address)
  c.chain = Rails.env.production? ? "base" : "base-sepolia"
end

同一份程式,dev 跑 testnet(sepolia),prod 跑 mainnet(base)。部署時不用改任何東西。Claude 也不會「忘了切換」,因為切換不需要它。

同樣的邏輯用在 basescan_tx_url、Plan 可見性(dev-only plans 正式環境不顯示)、Stripe price ID 選擇……凡是 dev 和 prod 不一樣的設定,都用 Rails.env 翻,不要依賴部署時記得改。

最後一道:人工走一次真實流程

四道護欄搭完,第一次真金白銀的支付仍然必須你自己點一次。

上一篇《讓 Claude 除錯玄學 bug》講過,我在正式環境第一次點 x402 支付,console 直接報 invalid_string at payTo——錢包位址第 43 位是一個從中文輸入法混進來的全形問號。前綴校驗擋不住(0x 開頭沒變),測試抓不到(因為測試不發真實交易),只有真實點一次才暴露。

部署的護欄不是給 Claude 看的,是給你看的——把可驗證的東西(前綴、長度、env 切換)自動化,省下的人工注意力用來盯那些自動化擋不住的真實互動。


讓 Claude 做部署,完整流程:

  1. 分開 dev 和 prod 的 credentials 檔 + .key 檔。
  2. 每個要塞的值寫一個一次性 Ruby 腳本,用 EDITOR=script 塞進去,立刻 rm。
  3. 塞完立刻用 Rails runner 回讀,帶前綴/長度校驗。
  4. 執行時差異全部用 Rails.env 翻轉,不要部署時手動切。
  5. 第一次點真錢的按鈕,自己點。

部署不是「更危險的寫程式」,部署是「寫錯了沒人馬上告訴你」的寫程式。護欄的作用是把「沒人告訴你」變成「15 秒內就能告訴你」。