Токен бота, пароль базы, API-ключ платёжной системы — секрет, попавший в исходный код, это утечка, которая ждёт своего часа. Достаточно открытого репозитория или одного скриншота, и ключ скомпрометирован. Разберём, где хранить секреты приложения и что делать, если ключ уже утёк.
Почему .env не коммитят
Файл .env с секретами добавляют в .gitignore и никогда не коммитят. Причины три:
- История Git помнит всё. Удалить файл следующим коммитом недостаточно — секрет остаётся в истории и доступен в любом старом коммите. Чтобы вычистить его полностью, нужно переписывать историю (
git filter-repo, BFG) и инвалидировать форки и клоны. - Публичные репозитории сканируются. Боты находят ключи в открытых репозиториях за секунды после пуша. Многие сервисы (включая GitHub) сами сканируют коммиты на утёкшие токены.
- Секрет в коде нельзя сменить без релиза. Конфигурация, вшитая в исходники, меняется только новым деплоем — это неудобно и опасно.
Проверить, не попал ли .env под версионный контроль:
git ls-files | grep -E '\.env$'
Пустой вывод — хорошо.
Переменные окружения — базовый способ
Стандарт хранения конфигурации — переменные окружения (принцип twelve-factor). Приложение читает их из окружения процесса:
import os
DATABASE_URL = os.environ["DATABASE_URL"] # явная ошибка, если не задано
Локально переменные удобно держать в .env (через python-dotenv, dotenv для Node.js или встроенный --env-file), а на сервере — задавать в окружении процесса. Файл .env остаётся только на машине разработчика и в .gitignore.
Шаблон без секретов: .env.example
Чтобы новый разработчик знал, какие переменные нужны проекту, в репозиторий кладут .env.example — список ключей без значений:
DATABASE_URL=
SECRET_KEY=
BOT_TOKEN=
Сам .env со значениями остаётся в .gitignore. Так состав конфигурации задокументирован и виден в репозитории, а реальные секреты в него не попадают. На сервере значения подставляются из окружения процесса.
Если секрет уже утёк
Главное правило: утёкший секрет считается скомпрометированным навсегда. «Удалить коммит» проблему не решает — ключ мог быть склонирован, закэширован и проиндексирован. Единственный надёжный путь — ротация:
- Выпустить новый ключ или токен.
- Отозвать старый (для Telegram-бота — через
@BotFather, для БД — сменить пароль, для API — revoke в панели сервиса). - Обновить переменную окружения в проде.
- Отдельно — вычистить секрет из истории Git, чтобы он не светился дальше.
Порядок важен: сначала ротация, потом чистка истории. Чистка без ротации бесполезна.
Где задавать секреты на сервере
- systemd — через
EnvironmentFile=/path/.env. Файл читается какKEY=valueбуквально, без шелл-синтаксиса. - Docker — через переменные окружения контейнера; для build-time секретов —
--secret, а неARG. - Managed-платформа — в панели проекта; платформа сама прокидывает переменные в процесс.
Чего делать не стоит — передавать секрет аргументом командной строки (--token=...): он виден в списке процессов (ps) и в истории shell.
Секреты в CI/CD
Пайплайн сборки и деплоя тоже не должен содержать ключей в коде. Их кладут в секреты CI (GitHub Actions secrets и аналоги) и прокидывают в окружение нужного шага. Две частые ошибки: вывести секрет в лог сборки (логи CI доступны команде, а иногда и публично — секрет в логе считается утёкшим) и прокинуть прод-секреты в пайплайны, которые запускаются на pull request из форков.
Когда .env перерос: менеджеры секретов
Для команды и нескольких окружений переменных окружения становится мало: нужны аудит доступа, ротация и единое хранилище. Тогда подключают менеджер секретов (HashiCorp Vault, секрет-хранилища облаков). Приложение получает секреты из него на старте, а не из файла. Для одного проекта это избыточно — переменных окружения достаточно.
Частые грабли
.envвсё-таки закоммичен. Проверятьgit ls-files; при утечке — ротация, не просто удаление.- Секрет в логах. Печать конфигурации или объекта запроса в лог уносит туда и ключи. Логи тоже хранятся и пересылаются.
- Секрет в слое Docker-образа.
ENV SECRET=...иARGвDockerfileостаются в слоях образа и видны при его инспекции. Для сборки используют монтируемые секреты. - Секрет в клиентском коде. Ключ, попавший во фронтенд-бандл, виден любому в браузере. Секреты живут только на сервере.
Чеклист
.envв.gitignore, под версионным контролем секретов нет (git ls-files).- Конфигурация читается из переменных окружения, а не из кода.
- На сервере секреты заданы через окружение, не аргументом командной строки.
- При утечке — ключ ротируется, а не «удаляется коммитом».
- Секреты не печатаются в логи и не попадают в Docker-слои и клиентский бандл.
Managed-платформа упрощает работу с секретами: переменные окружения задаются в панели проекта и прокидываются в процесс при деплое — без .env на сервере, ручного EnvironmentFile и риска закоммитить ключ. На Hostim секреты и DATABASE_URL хранятся в настройках проекта и доступны приложению как обычные переменные окружения.