Free

Membiarkan Claude Deploy ke Production

Error saat deploy tak bergejala. Empat pengaman — vault terpisah, skrip EDITOR, verifikasi baca-ulang, switch runtime by env — supaya Claude berguna tanpa membakar semuanya.


Perbedaan terbesar antara deploy dan menulis kode: deploy itu sekali jalan, berisiko tinggi, dan menyakitkan untuk di-rollback. Tulis satu baris kode yang rusak, sekali test ketangkep. Tulis satu baris credentials yang rusak, baru tahu saat pembayaran nyata pertama gagal — user sudah meng-charge kartu, uang tidak masuk rekening kamu, log penuh 400.

Baru-baru ini saya bawa how2claude dari dev lokal ke production bareng Claude: akun Stripe live, wallet x402 mainnet, Google OAuth, Kamal secrets. Claude tidak tahu nilai mana untuk test dan mana yang real. Tidak tahu bahwa sk_live_ vs sk_test_ berbeda satu huruf dengan konsekuensi kiamat. Pengaman kamu yang pasang.

Pengaman #1: Pisahkan file credentials untuk dev dan prod

Default Rails adalah config/credentials.yml.enc, didekripsi oleh config/master.key. Kalau kamu pakai default ini, kamu kalah.

Kalau kamu taruh sk_live_xxx di file itu lalu jalankan test lokal, kode test-mu memakai Stripe production. Setiap kali test jalan, kartu beneran kecharge.

Pisahkan jadi dua:
- config/credentials.yml.enc + config/master.key: untuk dev/test, simpan sk_test_xxx
- config/credentials/production.yml.enc + config/credentials/production.key: untuk prod, simpan sk_live_xxx

Kedua file .key di .gitignore, kedua .enc di-commit. production.key hanya ada di mesin deploy.

Lalu Kamal secrets harus menunjuk ke file yang benar:

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

Di dalam container, RAILS_MASTER_KEY sekarang menunjuk ke kunci production, dan yang didekripsi adalah credentials production. Saya perhatikan Claude menulis baris ini — template default Kamal adalah config/master.key (yang dev), yang diam-diam deploy Stripe key dev ke production.

Pengaman #2: Gunakan skrip EDITOR, bukan copy-paste

bin/rails credentials:edit --environment production membuka editor interaktif. Claude tidak bisa mengoperasikan editor interaktif. Kamu juga tidak mau paste manual selusin secret (satu typo merusak semua).

Gunakan pattern ini:

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 kira-kira seperti ini:

# ARGV[0] adalah path ke file YAML sementara yang sudah didekripsi
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 menulis YAML yang sudah didekripsi ke file sementara, memanggil "EDITOR"-mu dengan path itu sebagai argumen, lalu mengenkripsi ulang saat keluar. "EDITOR"-mu sebenarnya hanya skrip Ruby yang secara bedah memodifikasi satu kunci lalu menyimpan.

Kenapa ini bagus:
- Presisi: hanya menyentuh stripe.webhook_secret, tidak yang lain.
- Idempotent: menjalankan dua kali sama dengan sekali.
- Bisa diaudit: skripnya adalah diff-nya. Claude menulis, kamu lirik sekali, kamu tahu persis apa yang akan diubah.
- Hilang saat dihapus: setelah rm, tidak ada credential plaintext di disk, tidak ada paste whsec_... di shell history.

Satu skrip per credential: set_stripe_live_key.rb, set_webhook_secret.rb, set_price_ids.rb, set_wallet_address.rb. rm masing-masing tepat setelahnya.

Pengaman #3: Verifikasi dengan baca-ulang via Rails runner

Setelah menulis, jangan percaya bahwa penulisannya benar. Baca lagi:

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
"

Kuncinya adalah cek prefix dan length, bukan hanya mencetak nilainya.

  • Stripe live secret selalu berprefix sk_live_. Kalau kamu baca ulang sk_test_, Claude menaruh test key di file prod — bug yang tidak akan kamu temukan sampai pembayaran nyata pertama.
  • Webhook secret selalu berprefix whsec_. Format benar atau salah kelihatan sekilas.
  • Alamat wallet EVM selalu 42 karakter, 0x.... Panjang salah berarti ada karakter lain nyelonong masuk.

Menambahkan cek prefix memblokir sebagian besar typo, field hilang, dan environment tercampur.

Pengaman #4: Switch di runtime berdasarkan env, jangan manual saat deploy

Menggoda untuk berpikir "saya ubah network ke mainnet sebelum deploy". Switch semacam itu bergantung pada memori manusia. Cepat atau lambat membakarmu.

Tanam aturannya di 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

Kode yang sama, testnet (sepolia) di dev, mainnet (base) di prod. Tidak ada yang perlu diubah saat deploy. Claude tidak bisa "lupa switch" karena switch bukan tugasnya.

Trik yang sama untuk basescan_tx_url, visibilitas Plan (plan dev-only tidak tampil di prod), pemilihan Stripe price ID, dan seterusnya. Apa pun yang berbeda antara dev dan prod, flip dengan Rails.env. Jangan bergantung pada mengingatnya saat deploy.

Langkah terakhir: jalankan flow nyata sendiri

Dengan keempat pengaman terpasang, kamu tetap harus mengklik pembayaran uang nyata sendiri untuk pertama kalinya.

Di artikel sebelumnya Mendebug Bug Senyap dengan Claude, saya cerita tentang klik pembayaran x402 pertama di production: invalid_string at payTo di console. Karakter ke-43 dari alamat wallet adalah tanda tanya fullwidth yang nyelonong dari IME Mandarin. Cek prefix tidak bisa menangkap (0x tetap ada), test tidak bisa menangkap (test tidak memicu transaksi nyata), hanya klik nyata yang memunculkannya.

Pengaman deploy bukan untuk Claude. Itu untukmu — otomatisasi yang bisa diotomatisasi (prefix, panjang, flip env) supaya perhatian yang kamu hemat masuk ke interaksi nyata yang tidak bisa ditutup otomatisasi.


Flow lengkap Claude-deploy:

  1. Pisahkan credentials dev dan prod ke file .enc + .key yang terpisah.
  2. Untuk setiap nilai yang mau di-set, tulis skrip Ruby sekali pakai, umpan via EDITOR=script, hapus langsung.
  3. Setelah setiap penulisan, baca ulang dengan Rails runner, cek prefix dan panjang.
  4. Flip semua perbedaan dev/prod lewat Rails.env, bukan saat deploy.
  5. Klik tombol uang nyata sendiri di kali pertama.

Deploy bukan "menulis kode dengan stakes lebih tinggi". Deploy adalah "menulis kode di mana tidak ada yang bilang kamu salah". Pengaman ada untuk mengubah "tidak ada yang bilang" menjadi "kamu tahu dalam 15 detik".