デプロイのミスは誰も教えてくれない。credentials 分離、EDITOR スクリプト、読み戻し検証、env 切り替えの 4 本のガードレールで Claude を使い切る。
デプロイとコードを書くことの一番大きな違いは、デプロイが一発勝負で、ハイステークスで、ロールバックがしんどいことだ。コードを 1 行間違えてもテストが一発で捕まえる。credentials を 1 行間違えると、最初の実決済が失敗するまで気づかない——そのときにはユーザーはカードを切っていて、お金はあなたの口座には届かず、ログは 400 で埋まっている。
最近 Claude と how2claude をローカル開発から本番に上げた:Stripe の本番アカウント、x402 のメインネットウォレット、Google OAuth、Kamal secrets。Claude はどの値がテスト環境のもので、どれが本物かを知らない。sk_live_ と sk_test_ が 1 文字違いで世界が終わることも知らない。ガードレールは自分で立てる。
Rails のデフォルトは config/credentials.yml.enc、復号鍵は config/master.key。このデフォルトを使う時点で負け。
sk_live_xxx をこのファイルに入れてローカルでテストを走らせると、テストコードが本番 Stripe を叩く。テスト 1 回ごとに実際にカードが切られる。
2 つに分ける:
- 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 を本番にデプロイする。
bin/rails credentials:edit --environment production はインタラクティブなエディタを開く。Claude はインタラクティブエディタを動かせない。そして十数個の鍵を手でコピペしたくもない(1 つのタイポで全滅)。
このパターンを使う:
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 スクリプトで、キーを 1 つだけ正確に書き換えて保存する。
メリット:
- 正確:stripe.webhook_secret だけを触る、他は触らない。
- 冪等:2 回走らせても結果が同じ。
- 監査可能:スクリプト自体が変更の diff。Claude が書いたスクリプトを一目見ればなにを変えるかわかる。
- 削除したら消える:rm の後、ディスクに平文 credential が残らないし、シェル履歴に whsec_... のコピペ痕跡も残らない。
credential ごとにスクリプトを作る:set_stripe_live_key.rb、set_webhook_secret.rb、set_price_ids.rb、set_wallet_address.rb。書き終わったら即 rm。
書いた後、正しく書けたことを信用しない。読み戻す:
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
"
大事なのはプレフィックスと長さの検証で、値を表示して目視確認することではない。
sk_live_ で始まる。読み戻したら sk_test_ だったら、Claude が test key を prod ファイルに入れたということ——最初の実決済まで気づかない。whsec_ で始まる。形式が合っているかは一目瞭然。0x...。長さが違うなら、別の文字が紛れている。プレフィックス検証を足すだけで、タイポ、フィールド抜け、環境混入の大半をブロックできる。
「デプロイ前に network を mainnet に変えよう」と考えがち——こういう切り替えは人の記憶頼みで、いつか必ず事故る。
ルールは 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 はテストネット(sepolia)、prod はメインネット(base)。デプロイ時に何も変えなくていい。Claude が「切り替えを忘れる」こともない、切り替えが Claude の仕事じゃないから。
basescan_tx_url、Plan の可視性(dev 限定 Plan は本番に出ない)、Stripe price ID の選択などにも同じ手を使う。dev と prod で違う設定は全部 Rails.env で反転させる、デプロイ時に覚えておくことに頼らない。
4 本のガードレールを揃えても、最初の実決済クリックは自分でやる。
前回《Claude にサイレントバグをデバッグさせる》で書いたとおり、本番で最初に x402 の支払いをクリックしたら、console にいきなり invalid_string at payTo——ウォレットアドレスの 43 文字目が、中国語 IME から紛れ込んだ全角の疑問符だった。プレフィックス検証はすり抜ける(0x のままだから)、テストも捕まえない(テストは実取引を送らない)、実際にクリックして初めて表面化する。
デプロイのガードレールは Claude のためじゃない、あなたのためにある——自動化できるもの(プレフィックス、長さ、env 切り替え)は自動化して、節約した注意力を自動化が届かない実インタラクションに向ける。
Claude にデプロイをやらせる、完全フロー:
.key を分ける。EDITOR=script で投入、即 rm。Rails.env で反転、デプロイ時の手切り替えに頼らない。デプロイは「よりハイステークスなコーディング」じゃない、「間違えても誰も即座に教えてくれないコーディング」だ。ガードレールの役割は、「誰も教えてくれない」を「15 秒以内にあなたが知る」に変えること。