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

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

- Статус документа: working spec
- Актуально на: 31 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: при изменении deploy-контура, инфраструктуры, rollback-модели или runtime topology
- Область применения: эксплуатационный и migration-слой production-инфраструктуры проекта
- Связанные документы:
  - [Текущее состояние](../project/current-state.md)
  - [Roadmap](../project/roadmap.md)
  - [Инженерные принципы](../governance/engineering-principles.md)

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

Этот документ фиксирует профессиональный план перехода 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-контур

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

```text
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 из корня в отдельный каталог, например:

```text
deploy/
  compose/
    docker-compose.runtime.yml
  env/
  scripts/
```

4. Убрать неоднозначность между старым и новым docker-контуром.
5. Зафиксировать 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.
