Помилки деплою без симптомів. Чотири огорожі — розділені vault'и, EDITOR-скрипти, перевірка зворотним читанням, рантайм-фліп за env — щоб використовувати Claude, не спаливши все.
Головна відмінність деплою від написання коду: деплой — один постріл, високі ставки, відкат болючий. Напишеш зламаний рядок коду — один прогін тестів його ловить. Напишеш зламаний рядок credentials — дізнаєшся лише при першому реальному платежі: користувачі списали з картки, гроші не дійшли до твого рахунку, логи — суцільні 400.
Нещодавно перевів how2claude з локального dev'у в продакшен разом із Claude: Stripe live-акаунт, x402 mainnet гаманець, Google OAuth, Kamal secrets. Claude не знає, які значення тестові, а які справжні. Не знає, що sk_live_ vs sk_test_ — різниця в одну літеру з апокаліптичними наслідками. Огорожі ставиш ти.
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: prod, тримає sk_live_xxx
Обидва .key в .gitignore, обидва .enc закомічені. 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 інтерактивний редактор не веде. Ти теж не хочеш вручну вставляти десяток секретів (одна опечатка все ламає).
Використовуй цей патерн:
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 немає plaintext 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
Той самий код, testnet (sepolia) в dev, mainnet (base) в prod. При деплої нічого міняти не треба. Claude не може «забути перемкнути», бо перемикати — не його робота.
Той самий прийом для basescan_tx_url, видимості Plan (dev-only плани не рендеряться в prod), вибору Stripe price ID тощо. Усе, що відрізняється між dev і prod, перевертай через Rails.env. Не покладайся на те, що згадаєш при деплої.
Навіть із чотирма огорожами перший клік платежу справжніми грошима — за тобою.
У попередній статті Налагоджуємо тихі баги з Claude я розповів про перший клік x402-платежу в проді: invalid_string at payTo у консолі. 43-й символ адреси гаманця — знак питання повної ширини, що залетів із китайського IME. Перевірки префіксу його не ловили (0x на місці), тести не ловили (тести не стріляють реальними транзакціями), лише реальний клік витяг його назовні.
Огорожі деплою — не для Claude. Вони для тебе — автоматизуй те, що можна автоматизувати (префікси, довжини, env-перевороти), щоб збережена увага йшла на реальні взаємодії, які автоматизація не покриває.
Повний флоу деплою з Claude:
.enc + .key файли.EDITOR=script, одразу видаляй.Rails.env, не при деплої.Деплой — це не «писати код із вищими ставками». Деплой — це «писати код, де ніхто не каже тобі, що ти неправий». Огорожі існують, щоб перетворити «ніхто не каже» на «ти знаєш за 15 секунд».