Qadam Roadmap
проектdocs/operations/docker-contour-migration-plan.md

План перехода проекта на docker-контур

Обновлён 1 апр. 2026 г., 12:41 · 0 комментариев

План перехода проекта на docker-контур

Паспорт документа

  • Статус документа: working spec
  • Актуально на: 31 марта 2026 года
  • Владелец: backend/platform-команда
  • Пересмотр: при изменении deploy-контура, инфраструктуры, rollback-модели или runtime topology
  • Область применения: эксплуатационный и migration-слой production-инфраструктуры проекта
  • Связанные документы:

Цель документа

Этот документ фиксирует профессиональный план перехода Qadam с текущего host-based runtime на управляемый docker-контур.

Под docker-контуром в этом документе понимается не просто наличие Dockerfile, а полный управляемый delivery/runtime-процесс, в котором:

  • приложения собираются в версионируемые контейнерные образы;
  • сервер разворачивает образы, а не исходники из git;
  • деплой, rollback и smoke-проверки стандартизированы;
  • frontend и backend могут релизиться независимо;
  • окружение становится повторяемым для staging и production.

Ключевой вывод

Переход на docker-контур нужен, но делать его нужно поэтапно.

Правильный путь:

  1. Сначала контейнеризировать web, api и migrate.
  2. На первом production-переходе не трогать одновременно ingress, TLS и stateful services.
  3. Только после стабилизации контейнерного runtime решать, нужно ли контейнеризировать nginx, redis и postgres.

Попытка перевести всё сразу в контейнеры повышает риск outage без реальной пользы на первом этапе.

Текущее состояние проекта

Что уже есть

  • в репозитории есть актуализированный Dockerfile для apps/api;
  • в репозитории есть Dockerfile для apps/web;
  • в qadam-core/deploy/compose добавлен первый канонический manifest docker-compose.api-runtime.yml для api, migrate и seed;
  • для qadam-core/api уже добавлены канонические helper scripts deploy/scripts/build-api-image.sh и deploy/scripts/smoke-api-runtime-compose.sh, которые стандартизируют image tag и compose-based shadow smoke на текущем host runtime;
  • для qadam-core/api уже подтверждён рабочий registry namespace git.2fab.app/eldar/qadam-core-api, а publish/pull контур больше не держится на ручном docker tag && docker push;
  • для qadam-web уже добавлены лёгкий runtime Dockerfile, .dockerignore, compose manifest deploy/compose/docker-compose.web-runtime.yml и helper scripts deploy/scripts/build-web-image.sh, deploy/scripts/publish-web-image.sh, deploy/scripts/smoke-web-runtime-compose.sh;
  • для qadam-web уже подтверждён рабочий registry namespace git.2fab.app/eldar/qadam-web-app, а registry-backed shadow smoke проходит на 127.0.0.1:3002 без локального next build на production-хосте;
  • в корне лежит legacy docker-compose.yml;
  • у приложения есть health endpoints;
  • на сервере уже установлен Docker Engine, а его data-root вынесен в /mnt/qadam100gb/docker.

Что является реальным production-состоянием

На текущем сервере production сейчас работает в смешанном runtime-контуре:

  • публичный qadam-api уже переведён на qadam-api-container.service и обслуживается container runtime на 127.0.0.1:5002;
  • legacy qadam-api.service оставлен установленным как rollback-контур на 127.0.0.1:5001;
  • qadam-web запущен как systemd-сервис;
  • для qadam-web уже существует проверенный shadow runtime на 127.0.0.1:3002, но production upstream пока остаётся на host-level qadam-web.service;
  • nginx работает на хосте;
  • PostgreSQL и Redis работают вне Docker;
  • Docker Engine присутствует и уже используется как production runtime для api, но web и roadmap-сервис пока ещё не переведены на container delivery.

Что это означает

  • текущий docker-compose.yml и Docker-артефакты не являются каноническим runtime-контуром production;
  • production всё ещё частично зависит от git pull, локальной сборки на сервере и host-level состояния из-за qadam-web и roadmap-сервиса;
  • rollback для api уже стандартизирован по image tag и nginx upstream, но общий rollback-контур ещё не унифицирован для всех приложений;
  • будущий вынос frontend в отдельную репу без docker-контура будет неполным с точки зрения delivery.
  • при этом backend API уже прошёл дальше baseline: image публикуется в registry, qadam-api-container.service реально обслуживает production-трафик, а host nginx использует named upstream qadam_api_backend.

Почему проекту нужен docker-контур

1. Повторяемость окружения

Контейнерный runtime уменьшает расхождения между локальной разработкой, staging и production.

2. Иммутабельный деплой

Сервер должен получать не исходники, а конкретный образ с тегом. Это делает rollout и rollback предсказуемыми.

3. Разделение frontend и backend delivery

После выноса frontend в отдельную репу обе части должны поставляться независимо. Контейнерные образы лучше всего подходят для такого разделения.

4. Упрощение инфраструктурной автоматизации

С docker-контуром проще строить:

  • image build;
  • registry-based deploy;
  • healthcheck orchestration;
  • preview/staging среды;
  • контролируемый rollback.

Принципы миграции

1. Не мигрировать stateful и stateless слои в один шаг

Stateless-сервисы:

  • web
  • api
  • migrate

Stateful-сервисы:

  • PostgreSQL
  • Redis
  • TLS state
  • persistent storage

На первом этапе мигрируем только stateless-сервисы.

2. Репозиторий и runtime должны быть разделены

После перехода сервер не должен собирать приложение из рабочего дерева /data/uzbek. Production должен получать только image tag и env.

3. Один источник истины по деплою

Сейчас в проекте смешаны host runtime и Docker-артефакты. После migration должен остаться один канонический путь деплоя.

4. Rollback обязан быть проще, чем сейчас

Если после перехода rollback не становится проще, migration сделан неправильно.

5. Secrets и object storage credentials остаются runtime-only

  • production secrets не должны попадать в Dockerfile, image layers или git-managed compose manifests;
  • credentials для PostgreSQL, Redis, JWT, Telegram и будущего S3/object storage должны передаваться только через runtime env/secrets;
  • переход на docker-контур не меняет это правило, а делает его строже.

Рекомендуемое целевое состояние

Этап 1. Практический docker-контур

Рекомендуемая первая целевая модель:

Internet
  -> Host Nginx
    -> web container
    -> api container

Host / Managed services:
  - PostgreSQL
  - Redis
  - Let's Encrypt / certbot

Это лучший первый этап, потому что:

  • минимальный риск;
  • не ломает текущий ingress;
  • не требует одновременно переносить TLS;
  • позволяет сразу перейти на image-based delivery;
  • полностью совместим с отдельным frontend-репозиторием.

Этап 2. Расширенный docker-контур

После стабилизации можно отдельно решить:

  • переносить ли redis в контейнер;
  • переносить ли nginx в контейнер;
  • нужен ли контейнерный certbot;
  • есть ли смысл переносить PostgreSQL в Docker или правильнее оставить его host-managed / managed service.

Рекомендация по PostgreSQL

Для production не рекомендуется переносить PostgreSQL в Docker в тот же пакет работ, где проект впервые переводится на контейнерный runtime приложений.

Связь с выносом frontend в отдельную репу

Переход на docker-контур и вынос frontend в отдельную репу должны быть согласованы.

Итоговая модель должна быть такой:

  • backend-репозиторий собирает свой образ api;
  • frontend-репозиторий собирает свой образ web;
  • серверный deploy-контур разворачивает оба образа независимо;
  • ingress остаётся единым и маршрутизирует /api/* и /*.

То есть repo split и docker-контур не заменяют друг друга, а дополняют.

Фаза 0. Инвентаризация и нормализация

Цель

Понять, какие Docker-артефакты уже пригодны, а какие являются legacy.

Работы

  1. Проверить актуальность Dockerfile для api и web.
  2. Проверить совпадение healthchecks и реальных runtime endpoints.
  3. Проверить env matrix:
    • build-time env;
    • runtime env;
    • secrets;
    • hostnames;
    • internal URLs.
  4. Зафиксировать, что именно остаётся на host-level в первой фазе migration.

Результат фазы

  • понятен реальный scope docker-перехода;
  • отделены актуальные артефакты от legacy.
  • для api эта фаза фактически закрыта: Dockerfile и runtime assumptions уже пересмотрены и подтверждены первым smoke.

Фаза 1. Канонический container runtime для приложений

Цель

Подготовить production-ready контейнерный runtime для web, api и migrate.

Работы

  1. Канонизировать Dockerfile:
    • reproducible build;
    • runtime user;
    • health endpoints;
    • стартовые команды;
    • migrations.
  2. Подготовить канонический deployment manifest.
  3. Рекомендуется вынести production deploy manifests из корня в отдельный каталог, например:
deploy/
  compose/
    docker-compose.runtime.yml
  env/
  scripts/
  1. Убрать неоднозначность между старым и новым docker-контуром.
  2. Зафиксировать image naming и tagging strategy.

Результат фазы

  • есть канонический runtime manifest;
  • api уже можно поднять только из образа и env, а compose-based shadow smoke подтверждает этот runtime на текущем production-хосте без cutover;
  • для api уже существует source-controlled reversible cutover path и live production-трафик переведён на container runtime;
  • для web уже подтверждены build/publish/pull и registry-backed shadow smoke, но production cutover и rollback-процедура ещё не доведены до канонического состояния.

Фаза 2. CI/CD для образов

Цель

Сделать доставку на сервер image-based.

Работы

  1. Настроить сборку и push образов в registry.
  2. Развести pipeline для web и api.
  3. Добавить post-build smoke:
    • image собирается;
    • container стартует;
    • health endpoint отвечает.
  4. Настроить deploy pipeline, который:
    • выбирает image tag;
    • обновляет runtime manifest/env;
    • поднимает контейнеры;
    • запускает smoke-check;
    • даёт понятный rollback path.

Результат фазы

  • сервер получает образы из registry;
  • деплой перестаёт зависеть от локальной сборки на production-хосте.
  • Для api этот слой уже подтверждён end-to-end: registry push, registry pull smoke и production cutover runtime работают.
  • Для web этот слой уже подтверждён частично: registry publish/pull и shadow smoke работают, но пока не автоматизированы repo-side CI и не доведены до production cutover.

Фаза 3. Подготовка сервера

Цель

Подготовить production-машину к контейнерному runtime без выключения сервиса.

Работы

  1. Установить Docker Engine и Compose plugin.
  2. Подготовить каталоги runtime-контура:
    • env;
    • compose manifests;
    • volumes, если нужны.
  3. Подготовить host nginx к проксированию на container ports.
  4. Подготовить rollback-путь обратно на systemd, пока cutover не завершён.

Результат фазы

  • сервер готов к запуску контейнеров;
  • старый runtime ещё не выключен полностью.
  • На 31 марта 2026 года эта фаза практически закрыта для api:
    • Docker Engine установлен;
    • data-root переведён на /mnt/qadam100gb/docker;
    • host nginx умеет переключать qadam_api_backend между 5001 и 5002;
    • qadam-api-container.service и /etc/qadam/qadam-api-runtime.env уже используются в production;
    • host-level systemd runtime остаётся каноническим production-контуром только для qadam-web и qadam-roadmap.

Фаза 4. Staging и предбоевой прогон

Цель

Проверить docker-контур до production cutover.

Работы

  1. Поднять docker-runtime в staging или на отдельном server profile.
  2. Проверить:
    • web health;
    • api health;
    • migrations;
    • login / refresh / logout;
    • seller flow;
    • buyer flow;
    • roadmap portal.
  3. Проверить restart policy, логи и наблюдаемость.

Результат фазы

  • docker-контур подтверждён на реальных сценариях;
  • production cutover можно выполнять без гадания.

Фаза 5. Production cutover

Цель

Перевести production на container runtime приложений.

Работы

  1. Зафиксировать рабочий image tag для web и api.
  2. Подготовить container runtime целиком.
  3. Остановить старый systemd runtime приложений только после готовности container runtime.
  4. Переключить nginx на новые upstream.
  5. Выполнить smoke-check:
    • GET /api/v1/health
    • главная страница web
    • login
    • refresh
    • roadmap portal
  6. Для api считать этот шаг уже практически пройденным: production обслуживается через qadam-api-container.service, а финальный смысл фазы теперь смещён на web и общую унификацию app runtime.

Результат фазы

  • production работает на container runtime приложений;
  • host-based app runtime больше не является каноническим способом запуска.

Фаза 6. Постмиграционная зачистка

Цель

Убрать legacy-контур и зафиксировать новый источник истины.

Работы

  1. Обновить runbook.
  2. Удалить или архивировать устаревшие host-only deploy steps.
  3. Удалить неоднозначные скрипты и старые инструкции.
  4. Зафиксировать новую rollback-процедуру.
  5. Добавить регулярный smoke script и мониторинг.
  6. Для api вынести legacy qadam-api.service из статуса равноправного runtime и оставить его только как временный rollback path до полного закрытия CP-301.

Результат фазы

  • команда больше не путается между двумя способами деплоя;
  • новый docker-контур становится единственным каноническим runtime.

Что нужно решить до старта migration

Перед стартом нужно принять явные решения по этим вопросам:

  1. Оставляем ли nginx на хосте на первом этапе.
  2. Оставляем ли PostgreSQL и Redis на хосте или за пределами Docker.
  3. Где будут жить production env и secrets.
  4. Какой registry канонический для образов. На 31 марта 2026 года уже подтверждены два namespace в Gitea Container Registry:
    • git.2fab.app/eldar/qadam-core-api
    • git.2fab.app/eldar/qadam-web-app
  5. Какой manifest будет источником истины.
  6. Кто владеет cutover и кто утверждает rollback.

Основные риски

1. Одновременный перенос приложений, базы и ingress

Это самый частый путь к неуправляемому cutover. Его нужно избегать.

2. Ложное чувство готовности из-за наличия Dockerfile

Наличие Dockerfile ещё не означает наличие production-ready docker-контура.

3. Сохранение старого способа деплоя как “альтернативного”

Если после migration останутся равноправными и systemd, и Docker, команда быстро снова начнёт деплоить “кто как привык”.

4. Сервер продолжает собирать код из git

Если server всё ещё делает git pull && pnpm build, migration не завершён.

5. Отсутствие rollback по image tag

Если rollback требует ручной пересборки, значит image-based deployment не доведён до конца.

Критерии успеха

Переход на docker-контур считается завершённым, только если одновременно выполняются условия:

  • production web и api работают в контейнерах;
  • сервер не собирает приложение из исходников;
  • deploy выполняется по image tag;
  • rollback выполняется по image tag;
  • runbook описывает только новый канонический процесс;
  • smoke-check после деплоя автоматизирован хотя бы на базовом уровне;
  • frontend и backend можно выкатывать независимо.

Практический рекомендуемый порядок

  1. Канонизировать Docker runtime для web, api, migrate.
  2. Поднять image-based CI/CD.
  3. Установить Docker на сервер и подготовить host nginx.
  4. Прогнать staging.
  5. Сделать production cutover для приложений.
  6. Только после этого обсуждать контейнеризацию ingress, Redis и PostgreSQL.

Что делать не надо

  • Не переносить PostgreSQL в Docker в тот же пакет, где впервые контейнеризируются приложения.
  • Не менять одновременно repo split, auth model и runtime model.
  • Не сохранять git pull на production как нормальный путь обновления.
  • Не держать несколько равноправных production-контуров без чёткого статуса legacy.
  • Не считать существующий docker-compose.yml каноническим только потому, что он уже лежит в репозитории.

Рекомендуемый следующий шаг

Следующим инженерным пакетом нужно не “включить Docker на сервере”, а развить уже начатый app-level docker-контур:

  1. считать api baseline уже канонизированным и не тратить новый пакет на повторное изобретение того же контура;
  2. считать для web закрытыми build/publish/pull и shadow smoke, а следующий пакет направить уже на production cutover;
  3. перевести qadam-web на image-based production runtime и затем выровнять общий rollback/runbook;
  4. только после этого начинать cutover с host systemd на container runtime.