Почему .env-файлам не место в продакшене
Короткий ответ: .env на ноутбуке разработчика — удобный формат для локального
запуска, а .env на прод-сервере — это секрет, лежащий открытым текстом на
диске, чьи копии вы больше не контролируете. Он попадает в бэкапы, слои
образов, артефакты CI и домашние синхронизации; его читает любой процесс с
доступом к файловой системе; при утечке вы не узнаете об этом из самого файла.
Замена — не «спрятать файл получше», а убрать его из деплоя: сервис при старте
сам забирает значения из хранилища по своей машинной identity, и каждое такое
чтение остаётся в журнале.
Файл на диске не умеет молчать
Проблема .env в проде — не в формате, а в свойствах файла как носителя:
- Копии размножаются сами. Файл попадает в резервные копии хоста, в снапшоты
диска, в слой Docker-образа, если его забыли в
.dockerignore, в архив артефактов CI. Удалить оригинал — не значит удалить копии. - Прочитать может любой процесс на хосте. Права
600защищают от других пользователей, но не от процесса, работающего под тем же, — а именно под ним работает и всё, что вы запускаете рядом с приложением. - Файл не фиксирует чтение. Кто и когда открыл файл, не знает никто. Инцидент расследуется по косвенным следам, если расследуется вообще.
«У нас не файл, а переменные окружения» — не ответ
Переменные, выставленные в настройках оркестратора или CI, избавляют от файла,
но не от проблемы. Значение в переменной окружения видно через /proc/<pid>/environ
любому процессу с нужными правами, попадает в crash-дампы и отчёты об ошибках,
отображается в интерфейсе оркестратора всем, у кого есть доступ к проекту.
Главное — оно по-прежнему раздаётся человеком и хранится у стороннего
потребителя (CI, PaaS) в открытом виде: хранилище секретов подменяется
настройками деплоя, у которых нет ни аудита чтения, ни ротации.
Как выглядит доставка без файла
В env-master сервис — сущность первого класса со своей machine identity. При старте SDK аутентифицируется по client credentials, получает короткоживущий токен и забирает значения — шифртекст плюс ключ, обёрнутый под эту конкретную identity. Расшифровка происходит в памяти самого сервиса: машинный путь честно клиентский, сервер открытое значение в этом сценарии не производит (где граница на самом деле — написано на странице безопасности и в модели угроз, без слова «zero-knowledge»).
Дальше значения живут только в памяти процесса: на диске их нет, в образе их
нет, в настройках CI их нет. Каждое чтение — записано в
журнал, доступ identity можно сузить до списка
ключей (DB_*, а не всё окружение), а значение с видимостью restricted
человеку не показывается вообще — его получает только сервис.
Как переехать, не останавливая сервис
Переезд не требует переписывать приложение в один вечер:
- Заведите проект и перенесите значения.
envmaster import .env --dry-runпокажет план до записи и сам отметит, что похоже на секрет (быстрый старт). - Выдайте сервису identity со скоупом на его окружение и набор ключей.
- Подключите SDK — это замена чтения
process.envна клиент, который держит значения в памяти и сам обновляет токен. - Промежуточный шаг для легаси:
envmaster run -- <команда>передаёт значения дочернему процессу через окружение, не записывая их на диск, — приложение менять не нужно вообще. - Удалите файл с серверов, из CI и из бэкапов — теперь он никому не нужен.
Проверить, что в окружении ничего не потерялось, помогает валидация по схеме: она сверяет обязательные ключи с тем, что реально лежит в хранилище, и останавливает деплой до рантайма, а не после.
Аварийный выход остаётся открытым всегда: выгрузка окружения обратно в обычный
.env — одна команда CLI. Хранилище, из которого нельзя уйти, — это не
хранилище, а заложник.