Разработка, стейджинг, продакшн: три окружения и как их не перепутать
Как разделить конфигурацию, не дать тестовому ключу попасть в бой, а боевому — на ноутбук, и откуда брать данные для стейджинга.
Проект, начатый на платформе вроде Lovable или Replit, обычно живёт в одном окружении. Там же пишут код, там же показывают заказчику, там же регистрируются первые пользователи. Пока пользователей нет, это удобно. Как только они появляются, каждая правка становится экспериментом на живых людях.
Три окружения — разработка, стейджинг и продакшн — нужны, чтобы у ошибки была ограниченная зона поражения. Сломал что-то локально — пострадал ты. На стейджинге — команда. В продакшне — клиенты.
Чем они отличаются
- Разработка — ноутбук разработчика или превью ветки. Данные ненастоящие, внешние сервисы в тестовом режиме, можно всё.
- Стейджинг — копия продакшна по устройству: та же версия базы, тот же хостинг, те же настройки. Отличаются данные и ключи.
- Продакшн — то, чем пользуются люди. Доступ к нему есть у немногих, изменения приходят только через конвейер.
Главное требование к стейджингу — похожесть. Если в продакшне Postgres, а на стейджинге SQLite, стейджинг проверяет что-то другое.
Конфигурация живёт вне кода
Код один и тот же во всех окружениях, разница только в переменных окружения. Две привычки делают это надёжным.
Первая — одно место, где читается конфигурация. Модуль при старте читает переменные, проверяет, что все обязательные на месте, и падает с понятной ошибкой, если чего-то нет. Остальной код берёт настройки оттуда, а не из process.env в случайных местах.
Вторая — никаких проверок вида «если это продакшн, то…» по всему коду. Если поведение должно отличаться, это отдельная настройка с понятным именем, например EMAIL_DELIVERY=log, а не if (isProd) в обработчике письма. Такие проверки размножаются, и через полгода никто не знает, что ещё работает в продакшне иначе.
В репозитории лежит .env.example со всеми именами переменных и безопасными значениями, а настоящие файлы с ключами — нет.
Тестовые и боевые ключи
У большинства платёжных систем есть тестовый и боевой режимы с разными ключами, и это лучшая защита из возможных, если ею пользоваться. Стоит сделать так, чтобы приложение само отказывалось стартовать с неправильной парой:
const env = process.env.APP_ENV;
const key = process.env.STRIPE_SECRET_KEY ?? "";
if (env === "production" && !key.startsWith("sk_live_")) {
throw new Error("production needs a live Stripe key");
}
if (env !== "production" && key.startsWith("sk_live_")) {
throw new Error("live Stripe key outside production");
}
С почтой то же самое. Вне продакшна письма либо пишутся в лог, либо уходят в почтовую ловушку, которая принимает всё и никому не доставляет. С SMS — тестовые номера провайдера. Настоящее письмо настоящему клиенту со стейджинга — это не баг, а неловкий разговор.
Откуда брать данные
Соблазн скопировать боевую базу на стейджинг понятен: там «настоящие» данные. Но вместе с ними на стейджинг уезжают почты, телефоны и адреса клиентов, а доступ к стейджингу обычно шире, чем к продакшну. Кроме того, скопированные адреса превращают любую ошибку в настройке почты в рассылку по живым людям.
Лучше иметь скрипт начальных данных, который создаёт правдоподобный набор: пользователей разных типов, заказы в разных статусах, пограничные случаи вроде пустых профилей и очень длинных имён. Скрипт должен быть идемпотентным — повторный запуск не плодит дубли. Тогда любое новое окружение, включая превью ветки, поднимается за минуту.
Если без боевых данных не обойтись, например чтобы воспроизвести проблему с производительностью, их обезличивают до копирования, а не после.
Чтобы не перепутать вкладки
Звучит мелко, но работает: на стейджинге поверх интерфейса висит полоса с названием окружения, у него другая иконка во вкладке браузера и другой цвет в админке. Человек с пятью открытыми вкладками в конце дня не должен угадывать, где он сейчас удаляет запись.
SUBXCAT может разделить окружения в существующем проекте и написать скрипт начальных данных, чтобы новый стейджинг поднимался без ручной работы.