Методология Twelve-Factor App: принципы удобного деплоя приложений
Основы4 минРедакция Innovialabs
Коротко
Twelve-Factor App — это двенадцать правил, которые делают приложение одинаково запускаемым в любом окружении: код в одном репозитории, конфиг в переменных окружения, процессы без состояния, логи в stdout и минимум различий между dev и prod.
Коротко: что такое Twelve-Factor App
Twelve-Factor App — набор из двенадцати принципов для веб-приложений и сервисов, которые развёртываются в облаке, в контейнерах или на обычных серверах. Цель одна: приложение должно собираться, настраиваться и запускаться одинаково везде — на ноутбуке разработчика, на staging и в production — и легко масштабироваться добавлением процессов.
Методология не привязана к языку или фреймворку. Её идеи лежат в основе Docker, Kubernetes и большинства PaaS-платформ.
Все двенадцать факторов
| № | Фактор | Суть |
|---|---|---|
| 1 | Codebase | Одна кодовая база в системе контроля версий, много деплоев |
| 2 | Dependencies | Зависимости объявлены явно и изолированы |
| 3 | Config | Конфигурация хранится в переменных окружения |
| 4 | Backing services | БД, кэш, очереди — подключаемые ресурсы по URL |
| 5 | Build, release, run | Сборка, релиз и запуск строго разделены |
| 6 | Processes | Процессы без состояния, ничего не храним локально |
| 7 | Port binding | Сервис сам слушает порт и экспортирует HTTP |
| 8 | Concurrency | Масштабирование запуском дополнительных процессов |
| 9 | Disposability | Быстрый старт и корректное завершение |
| 10 | Dev/prod parity | Окружения максимально похожи |
| 11 | Logs | Логи — поток событий в stdout |
| 12 | Admin processes | Разовые задачи запускаются тем же кодом и окружением |
Конфиг через переменные окружения
Всё, что меняется между окружениями — адрес БД, ключи API, флаги — живёт вне кода. Проверка простая: можно ли открыть репозиторий публично, не раскрыв ни одного секрета?
import os
DATABASE_URL = os.environ["DATABASE_URL"]
REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
DEBUG = os.environ.get("DEBUG", "false") == "true"
Локально значения можно держать в файле .env (добавленном в .gitignore), а на сервере — передавать через оркестратор или менеджер секретов. Избегайте файлов config.production.py с захардкоженными значениями: это и утечка секретов, и рост числа «особых» окружений.
Процессы без состояния
Процесс приложения не должен хранить ничего важного между запросами: сессии, загруженные файлы, кэш выносятся в Redis, базу данных или объектное хранилище.
- Пользователь загрузил аватар — файл уходит в S3-совместимое хранилище, а не в папку
uploads/на диске. - Сессия хранится в Redis или в подписанной cookie, а не в памяти процесса.
Тогда любой экземпляр можно перезапустить или удалить, а нагрузку — распределить между несколькими копиями без «липких» сессий.
Логи как поток
Приложение не управляет файлами логов. Оно пишет события в stdout/stderr, а сбором, ротацией и хранением занимается среда: Docker, systemd, агент вроде Fluent Bit или Vector, система вроде Loki или ELK.
console.log(JSON.stringify({ level: "info", msg: "order created", orderId }));
Структурированный JSON удобно фильтровать и искать. Ротация файлов внутри контейнера, наоборот, приводит к потерянным логам и забитому диску.
Паритет dev и prod
Двенадцатифакторное приложение уменьшает три разрыва: по времени (код быстро доходит до production), по людям (разработчики участвуют в деплое) и по инструментам.
Типичная ошибка — SQLite локально и PostgreSQL на сервере. Мелкие различия в SQL всплывут только в production. Решение — поднимать локально те же сервисы тех же основных версий через Docker Compose.
Остальные факторы на практике
- Зависимости: lock-файлы (
package-lock.json,poetry.lock) в репозитории, никаких «глобально установленных» утилит. - Build, release, run: образ собирается один раз, получает тег, и именно он переходит из staging в production. Править код на сервере нельзя.
- Port binding: приложение само поднимает HTTP-сервер на порту из
PORT, а nginx или балансировщик стоят перед ним. - Disposability: обрабатывайте
SIGTERM— дождитесь текущих запросов, закройте соединения и выходите. - Admin processes: миграции и скрипты запускаются как отдельный процесс из того же релиза, например
docker compose run app python manage.py migrate.
Частые ошибки
- Секреты в репозитории или в Docker-образе.
- Файлы пользователей на локальном диске контейнера.
- Разные версии БД в dev и prod.
- Ручные правки на production-сервере в обход релиза.