← На главную

SecretSpec кладёт TOML в коммит и спасает от хаоса .env

31.07.2026 15:24 · hackernews

.env — один из самых успешных случайных костылей в разработке. Задумывался как замена трём export, а стал схемой конфигурации, хранилищем секретов, описанием окружения, интерфейсом CI и форматом деплоя. Environment variables хорошо умеют одно: доставлять строки в процесс. .env превратил доставку в источник правды, и именно здесь всё сломалось.

Проблема в том, что KEY=value не может сказать, обязателен ли параметр, секретный он или нет, можно ли коммитить, доступен только в проде или нужен одному сервису. Это требования, которые переживают любой процесс и ноутбук разработчика. .env хранит значения, но не описывает модель секретов приложения. Из-за этого DEBUG и STRIPE_API_KEY выглядят равноценно, хотя один — обычная настройка для Git, а другой требует доступа и ротации. Пустые значения тоже непонятны: это «необязательно» или «забыли»? Про boolean в dotenv спорят с 2015 года: "false" оказывается truthy.

Ещё .env подталкивает создавать файлы вроде .env.production для каждого окружения. Это повторяет группировку из Twelve-Factor App, которую авторы как раз пытались убрать. Каждое новое значение надо добавить в .env.example, README, код валидации и все реальные файлы. Один пропуск — и окружения разъезжаются.

Стандарта у .env нет. Node.js и python-dotenv не имеют спецификации, каждый парсер делает по-своему. python-dotenv раскрывает ${NAME}, но не $NAME. Node dotenv расширение переменных отдаёт другому инструменту. Docker Compose поддерживает свои операторы. Vite работает в другом порядке и предупреждает, что выражение не будет работать в shell. Даже # и кавычки трактуются по-разному: в Node dotenv 15 значение # в некавыченных строках сломали. Приоритеты тоже разные: Node dotenv отдаёт победу первому файлу, Docker Compose — последнему env_file, Vite — существующей переменной процесса. Всё зависит ещё и от тайминга: Vite подставляет VITE_* на этапе сборки, Bun умеет сам подхватывать .env и мешает Vite. Одна и та же строка может оказаться runtime-секретом, константой сборки или публичным значением в браузере.

dotenv советует не коммитить .env, но .gitignore не добавляет шифрования и контроля доступа. Файлы всё равно попадают в бэкапы, чаты, архивы, container build context и Nix store. Когда разработчик уходит, отозвать его доступ к секретам нельзя — каждый скопированный ключ остаётся копией. Даже если секрет лежит в 1Password, Vault или cloud secret manager, для dotenv-приложения его придётся скопировать в plaintext. Docker тоже не любит environment variables для секретов: они текут между контейнерами, поэтому он монтирует managed secrets как файлы. Процесс получает одну глобальную карту, и фронтенд-сборка, воркер и веб-сервис часто видят одни и те же секреты, хотя им нужны разные.

SecretSpec решает это так. В коммитируемый файл кладётся декларация на TOML: имя, описание, required, default. Секретных значений там нет. Хранилищем занимаются провайдеры, а secretspec check и secretspec run проверяют требования до старта приложения. Профили описывают реальные различия окружений как оверлеи на profiles.default. Scopes (0.17+) позволяют каждому сервису получать только свою подмножество. Есть metadata-only audit log.

Миграция постепенная: secretspec init --from dotenv:.env копирует имена без значений, старый файл может остаться провайдером через --provider dotenv:.env. Значения потом переносятся в keyring или Vault без изменения имён. .env можно оставить для обычных локальных настроек. Главная цель SecretSpec — вообще убрать environment variables для секретов.

Читать оригинал →