12-Factor App methodology — 12 принципов для cloud-native: codebase, dependencies, config (env vars), backing services, build/release/run, stateless processes, port binding, concurrency, disposability (graceful SIGTERM), dev/prod parity, logs to stdout, admin processes. Foundation для контейнеризации и Kubernetes. 4 сценария: anti-pattern config-in-code vs factor 3, stateless scaling (factor 6+8), graceful SIGTERM (factor 9), build/release/run + logs (factor 5+11). 2 ADR: где 12-factor остался релевантен в 2026, sticky sessions vs externalized state.
Heroku написал 12-factor в 2011 году — это был манифест того, как должно выглядеть приложение, чтобы PaaS мог его запустить без танцев с бубном: загрузил git push, платформа собрала, задеплоила, отскейлила, перезапустила, заменила версию, и тебе ничего не нужно знать про конкретный сервер. Прошло 15 лет, Heroku из главного героя стал legacy, его место заняли Kubernetes, Render, Cloud Run, Fly.io, Vercel — но 12 принципов никуда не делись. Наоборот: всё, что появилось после, либо построено поверх них (k8s буквально assumes 12-factor), либо ломается ровно в тех местах, где их игнорируют.
12-factor отвечает на простой вопрос: что отличает "приложение, которое можно задеплоить на любой облачной платформе" от "приложения, к которому привязан конкретный сервер"? Без этих принципов ты живёшь в мире, где deploy — это страшное событие раз в неделю, rollback — это "восстановим из бэкапа", autoscaling — теория из книжек, а sticky sessions нужны "потому что иначе ломается". С ними ты живёшь в мире, где pod может умереть в любой момент и это нормально, где deploy занимает 30 секунд и его делают по 20 раз в день, где staging получает тот же артефакт что и prod, отличаясь только env-vars, и где rollback — это kubectl rollout undo за 5 секунд.
Это не про "best practices ради best practices". Это про необходимое условие для горизонтального масштабирования, blue-green deploys, spot instances, multi-region failover, immutable infra. Любая из этих штук ломается, если процесс stateful (factor 6 нарушен), если config в коде (factor 3), если startup занимает минуту (factor 9), если логи пишутся в файлы (factor 11). 12-factor — это minimum viable cloud-native: не выполнил — дальше ничего не работает.
Представь, что облачная платформа — это бесстрастный планировщик, который в любой момент может: запустить ещё 10 копий твоего приложения, убить 3 случайных копии, перенести их на другой сервер, заменить версию на новую без даунтайма, перенаправить трафик в другой регион. Платформе всё равно, что внутри — она работает только с input/output границей: контейнер берёт env-vars на вход, биндится на порт, пишет в stdout, реагирует на SIGTERM. Всё, что выходит за эту границу — write в файл, in-memory state, hardcoded IP-адреса базы, локальный кэш — это песок в шестерёнки: платформа этого не видит, а значит не может ни сохранить, ни мигрировать, ни масштабировать.
12 принципов делятся на четыре группы по тому, что они защищают:
Деплоймент как единая операция (factors 1, 2, 5): одна codebase даёт один образ, который собирается один раз и катится во все окружения. Зависимости явные — никто не предполагает, что на хосте уже установлен curl нужной версии. Build/release/run строго разделены: build тяжёлый (compile, test, scan), release атомарный (image + config snapshot), run тривиальный (docker run). Это даёт immutable artifacts и rollback за секунды.
Конфигурация как внешняя инъекция (factors 3, 4): всё, что меняется между окружениями (DB URL, API keys, feature flags), приходит в process через env-vars. Backing services (Postgres, Redis, S3, Kafka) — это attached resources: app не знает, локальная это база или managed RDS, он знает только DATABASE_URL. Это даёт portability и secret rotation без передеплоя кода.
Процесс как stateless и disposable (factors 6, 8, 9): процесс не помнит ничего между запросами — состояние в Redis/Postgres. Конкурентность через процессы, не треды: добавить мощности = поднять ещё pod, не настраивать thread pool. Disposability: startup за <10s, graceful SIGTERM с draining за <30s. Это даёт horizontal scaling, zero-downtime deploys, spot instances.
Наблюдаемость и parity (factors 7, 10, 11, 12): app сам биндится на порт (app.listen(8080)), не нужен внешний веб-сервер. Dev и prod максимально похожи (тот же Postgres локально, не SQLite). Логи — это event stream в stdout, не файлы; собирает их платформа (Fluent Bit → Loki). Админ-задачи (миграции) — отдельные one-off процессы с тем же образом и env, не standalone скрипты. Это даёт debugging in production и уверенность, что миграция не сломает prod, потому что её прогнали в staging на той же базе.
Запомни главное: 12-factor — это не правила, это constraints, которые делают приложение совместимым с любой PaaS. Нарушаешь — теряешь конкретную capability платформы. Соблюдаешь — получаешь deploy, scale, restart, rollback, multi-region бесплатно.
Канвас разделён пополам — слева anti-pattern legacy "snowflake" app, справа 12-factor compliant cloud-native сервис. Это не два разных приложения — это одно и то же приложение, написанное двумя способами, с одинаковыми компонентами в разных ипостасях.
Anti-pattern блок (верхний левый): монолит-процесс (anti-app) на одном хосте, читающий config.yml из файловой системы (с production паролями прямо в git!), хранящий sessions в map в памяти процесса, пишущий логи в файл /var/log/app.log, и запускающийся 60 секунд (warm-up JVM, прогрев кэшей). Под ним infra-блок: sticky load balancer с ip_hash прибивает каждого user'а к конкретному pod'у, и Postgres с hardcoded host="10.0.1.5" прямо в коде. Все edges помечены тем, что именно нарушается: "config.yml from FS", "pinned to pod (ip_hash)", "host hardcoded".
12-factor блок (верхний правый): тот же app, но как stateless контейнер (good-app), читающий config из env-vars (good-env — DATABASE_URL, REDIS_URL), пишущий логи в stdout (good-stdout), биндящийся на
good-port — factor 7), и обрабатывающий SIGTERM с graceful drain (good-shutdown — factor 9). Под ним backing services (factor 4): Redis для сессий, Postgres через env URL, S3 для uploads, SQS/Kafka для background. Справа сверху — stateless LB (k8s Service / ALB) с round-robin (не sticky!), client, и kubelet, который управляет жизненным циклом pod'ов через liveness probes и SIGTERM на rolling deploys.
Pipeline блок (нижний левый): factor 5 в полном виде — codebase (git) → declared dependencies (package.json) → build (docker image) → release (image + config snapshot, immutable) → run (kubectl apply, spawn pods). Каждое следующее звено зависит только от предыдущего, изменения текут в одну сторону.
Scaling блок (нижний правый): factor 8 (concurrency через process model) — три web-pod'а (web-1/2/3), два worker-pod'а, и admin Job (factor 12: миграция БД как отдельный k8s Job с тем же образом и env). Worker'ы dequeue из общей queue, web-pod'ы обслуживают HTTP. Все pod'ы пишут в общий stdout-поток.
Edges рассказывают историю потоков: в anti-pattern client прибит к одному pod'у, который читает config с диска и пишет state в память. В 12-factor client попадает на любой pod, который на запросе тянет state из Redis, конфиг получил при startup из env, а на shutdown gracefully возвращает соединения. Различие между двумя половинами диаграммы — это разница между "приложение, которое можно сейчас выключить и включить заново без последствий" и "приложение, которое нельзя трогать, пока не сломается".
Четыре сценария показывают, что именно ломается при нарушении факторов и как это исправить. Каждый — про конкретный фактор плюс реальное операционное последствие.
config-anti-vs-factor3 — Config in code vs env (factor 3). Начинаем с config.yml в репозитории, содержащим db_password: prod_secret_xyz и aws_key: AKIA.... Главная боль — не сам факт hardcoded values, а что происходит дальше: захотели задеплоить в staging — нужно делать второй config-staging.yml, что вынуждает собирать второй образ myapp-staging:v1. Это сразу нарушает factor 10 (dev/prod parity): два разных артефакта = разное поведение, баг в prod не воспроизведётся в staging. Плюс git history содержит prod-пароли — rotation требует переписать историю репозитория или принять, что пароли скомпрометированы навсегда. 12-factor way: один образ myapp:v1 деплоится везде, env-vars (DATABASE_URL, AWS_SECRET_ACCESS_KEY) приходят из k8s ConfigMap/Secret. Ротация пароля = kubectl create secret + rolling restart за 5 минут.
stateless-scaling — Factor 6 + 8: in-memory sessions vs Redis. Демонстрирует каскад поломок от одного нарушенного фактора. Anti-pattern: sticky LB прибивает user'а к anti-app pod 1, sessions хранятся в sessions[user_id] в heap. Pod умирает (OOM / evict / deploy) → все sessions потеряны → 503 + logout. Scale out на 4-й pod → старые users всё равно идут на pod 1-3, новый pod получает 25% трафика, нагрузка неравномерна. 12-factor: round-robin LB шлёт каждый request на любой pod, pod на каждом запросе делает GET session:42 из Redis (~1ms). HPA видит CPU 80% → kubectl scale --replicas=10 → 7 новых pod'ов за 30s принимают трафик сразу. Scale-in убивает random pods — zero impact на users.
graceful-sigterm — Factor 9: disposability. Самый недооценённый фактор. Kubelet постит SIGTERM в PID 1, pod удаляется из Service endpoints (~1-5s через iptables). App ловит сигнал и делает по шагам: stop accepting connections (server.close()), wait for in-flight (12 текущих HTTP-запросов), close DB pool, close Redis, worker NACK in-flight messages (чтобы другой worker подобрал, не дублируя работу), process.exit(0) за 5-15s. Anti-pattern: handler'а нет → 30s grace period → SIGKILL → TCP RST на live connections → 5% requests = 502 во время deploy, background job duplicate processing = double charge. Бонус: без factor 9 нельзя использовать spot instances (AWS даёт 2-min termination notice — без graceful shutdown теряешь работу).
build-release-run — Factor 5 + 11: immutable releases + structured logs. Build (slow): compile, test, scan, docker build → image myapp:v1.2.3, content-addressed sha256, в registry. Release (atomic): release v42 = image v1.2.3 + ConfigMap revision 17 — immutable snapshot. Run (trivial): kubelet pulls image, injects env, exec PID 1. Rollback с broken v42 на v41 = kubectl rollout undo → instantly back, потому что release v41 содержит точно тот же image + config что был. Factor 11: app пишет console.log в stdout, Fluent Bit/Vector daemon на каждой ноде читает /var/log/containers/*.log и шипит в Loki/Datadog. Query trace_id="abc" показывает все logs across pods + correlated OpenTelemetry traces. Anti-pattern: log files → logrotate → pod evicted (disk pressure) → logs gone.
Два архитектурных решения вшиты в anti-app ноду как decisions — открой её на канвасе через [CALC] или hover, чтобы прочитать полностью.
ADR-001: 12-factor в 2026 — где остался релевантен, где устарел. 12-factor написан для мира, где app = один web dyno + один worker dyno + Postgres add-on. Сейчас Kubernetes, service mesh, serverless, edge computing, ML serving — нужна пересборка mental model. Остаётся критичным: factor 3 (config in env), 5 (build/release/run — основа CI/CD и k8s), 6 (stateless — без него нет horizontal scaling), 9 (disposability — pod может умереть в k8s в любой момент), 11 (logs as streams — pod stdout → Fluent Bit → Loki). Расширилось: factor 4 (backing services) включает service mesh, factor 8 (concurrency) = HPA/VPA + KEDA event-driven autoscaling, factor 10 (dev/prod parity) = Docker Compose локально + Tilt/Skaffold для k8s parity. Нужны оговорки: factor 1 (one codebase per app) ломается в monorepo с shared packages — современная интерпретация: "один deployment unit, шаренный код через packages OK"; factor 6 strict stateless конфликтует со stateful workloads (Postgres operator, Kafka — они НЕ stateless, и это правильно: StatefulSets); factor 12 admin processes стал k8s Jobs / init containers; serverless нарушает factor 7 (port binding) — Lambda не binds port, она invoked by event. Итог: применяй 12-factor как baseline checklist для stateless web services, но не пытайся натянуть на everything.
ADR-002: Sticky sessions vs externalized state — какой ценой нарушаем factor 6. Самый часто нарушаемый фактор. Соблазн: hold session/cache в памяти процесса (быстро, no network hop), sticky sessions через ip_hash балансера. Цена: restart процесса теряет sessions активных users (503); horizontal scaling не работает по-настоящему — добавил pod, но old users идут на old pod; auto-scaling down убивает random pods → теряются sessions; rolling deploy кикает половину users в один момент. Альтернатива: внешний store (Redis, DynamoDB), любой pod обслуживает любой request. Trade-off: +1ms network hop, +operational cost (Redis cluster). Когда sticky sessions legit: (a) WebSocket / SSE long-lived connections (consistent hashing на user_id, fallback на любой pod при failure); (b) ML inference с heavy in-memory model (5GB загружается за 30s — load один раз на pod, distribute work через consistent hashing); (c) collaborative editing с in-memory CRDT (Liveblocks/Yjs паттерн). При этом session-data всё равно дублируется в Redis для recovery. Никогда не использовать sticky sessions для обычных REST endpoints — это лень под видом оптимизации.
Heroku — родина 12-factor. Каждый dyno = process, который должен binding на $PORT, читать config из env, писать в stdout. Если приложение нарушает factor 7 — оно физически не стартует на Heroku.
Kubernetes — assumes 12-factor. ConfigMap/Secret для factor 3, Service+endpoints для factor 7, stdout collection через Fluent Bit для factor 11, SIGTERM + terminationGracePeriodSeconds для factor 9, Jobs для factor 12, HPA для factor 8. Если app не 12-factor, k8s даст deploy, но при первом scale-out / rolling update начнутся 503-ки.
Cloud Run / AWS Lambda — radical 12-factor: container получает request, обрабатывает, возможно убивается между запросами. Factor 6 (stateless) — единственно возможный режим работы. Factor 9 — обязателен (cold start <1s, иначе high latency). Factor 7 нарушен (Lambda не listens — она invoked) — единственное исключение, оправданное архитектурой.
Netflix — каждый из 1000+ микросервисов следует 12-factor (build pipeline через Spinnaker, immutable AMIs / containers, Eureka для backing service discovery). Это позволяет Chaos Monkey убивать случайные instances в prod без алертов.
Render / Fly.io / Railway — современные PaaS, явно требующие 12-factor. Документация буквально ссылается на 12factor.net.
12-factor monorepo (Vercel + Next.js): Vercel из git репозитория собирает immutable build (factor 5), деплоит как edge functions с env-vars (factor 3), stateless (factor 6), logs в Vercel dashboard через stdout (factor 11). Развёрнутая версия — это ровно та модель, которую описал Heroku 15 лет назад.
Sticky sessions для REST endpoints — самое частое. "Нам не хочется добавлять Redis, давайте включим session-affinity на ALB". Через полгода нельзя сделать rolling deploy без downtime, нельзя добавить HPA, нельзя использовать spot instances.
Config в Dockerfile (ENV DATABASE_URL=postgres://...) — псевдо-12-factor. Технически env var, но он зашит в образ → один образ на окружение → нарушение factor 10 (dev/prod parity). Правильно: env vars передаются на RUN, не на BUILD.
Логи в файлы внутри pod'а — /var/log/app.log на ephemeral storage. Pod evicted → logs gone. Debug impossible.
Long startup (>30s) — нарушает factor 9. Liveness probe fails, pod marked NotReady, во время rolling deploy capacity падает в 2 раза. Spot instances нельзя использовать. Невозможно быстро scale out под пиковую нагрузку.
Hardcoded backing service hosts — db_host = "10.0.1.5" в коде. Меняется адрес базы → нужен пересбор образа и redeploy. Failover на standby невозможен без передеплоя.
Stateful processes (cache в памяти, in-memory queues) — pod restart = data loss. Любая операция платформы (deploy, scale, evict) превращается в инцидент.
Не обрабатывать SIGTERM — TCP RST на live connections, DB transactions rolled back, queue messages duplicated. Если у тебя is_charge() в worker'е без graceful drain — поздравляю с double charges.
Migrations как standalone скрипт на dev-машине разработчика → разный env, разная Postgres version, "у меня работало". Правильно: k8s Job с тем же образом, теми же env vars, тем же CI pipeline.
Stateful workloads (Postgres, Kafka, Elasticsearch, ML training jobs с GB-state, video transcoding с локальным scratch space). Factor 6 (stateless) для них не работает, это их природа. Используй StatefulSets / operators / dedicated infrastructure. 12-factor для wrapper'ов вокруг stateful storage — да; для самого storage — нет.
Monorepo с 50 микросервисами — factor 1 (one codebase per app) буквально нарушается. Используй современную интерпретацию: "один deployment unit, shared code через packages OK". Не дроби monorepo на 50 git-репозиториев ради соответствия букве.
Embedded / IoT / desktop apps — нет облачной платформы, нет ephemeral processes, нет dynamic scaling. 12-factor решает проблемы, которых там нет.
Legacy системы с глубокой stateful архитектурой (старые ERP, SAP, унаследованные COBOL на mainframe). Переписать на 12-factor = переписать с нуля. Часто экономически бессмысленно. Wrap APIшкой и забудь.
HPC / batch jobs где state в shared filesystem — Slurm, MPI, scientific computing. Совсем другая модель. 12-factor попытка тут сломает производительность.
Когда compliance requires on-prem fixed infrastructure (банки, госы, военка). Если deploy = квартальное событие с подписанным change request, immutable artifacts и rolling updates просто не используются — overhead 12-factor compliance не окупится.