Перейти к основному содержимому

CI/CD: пайплайн и деплой

.gitea/workflows/ci.yml, репозиторий admin/catalog на git.termitsdigital.ru. Стадии: check → integration → deploy (только master) → verify.

Как запустить раннер и общая последовательность работы с пайплайном — в CLAUDE.md проекта, раздел «Пайплайн: единственный путь в прод». Здесь — устройство самого workflow.

check: линт, тесты, сборка​

  • npm ci идёт с отключённым audit и funding, лимит Node heap 2048 МиБ (:71).
  • Линт, unit-тесты и сборка идут последовательно (--concurrency=1) — раннеру не хватает памяти на параллельный прогон (:87).
  • Кэш Turbo ограничен потолком 3072 МиБ: сохраняются самые свежие записи, старый хвост удаляется (:44).
  • Тестовый .env собирается из шаблона.
  • CI добавляет рабочий каталог в доверенные Git-директории перед сборкой документации (:44).

integration: только для master​

Интеграционные тесты запускаются только для ветки master, на остальных ветках пропускаются (:91).

Redis в CI поднят, но тестируемым приложением не используется

Сервис Redis в CI действительно поднимается, и REDIS_URL передаётся в тестовое окружение — но NODE_ENV=test, при котором запускаются интеграционные тесты, отключает Redis cache/event/ workflow engine на стороне самого backend (см. NODE_ENV=test отключает часть модулей). Живым в интеграционных тестах остаётся только PostgreSQL — Redis в CI поднят «на всякий случай» и сейчас фактически простаивает.

Перед прогоном CI генерирует одноразовые валидные ключи шифрования для payment, shipping, МойСклад и MFA (:143) — реальные production-ключи в тестовое окружение не попадают.

Почему сервис БД называется postgres-localhost

Так CI обходит эвристику Medusa test-utils, которая иначе включила бы неподдерживаемый в тестовом окружении SSL (:95).

deploy: прямой вызов Dokploy API​

Деплой идёт через POST /api/trpc/compose.deploy с явно заданным composeId, не через webhook /api/deploy/compose/<токен> — вебхук берёт ветку из тела события git-провайдера, пустой POST ни с чем не совпадает, и Dokploy молча ничего не делает, отвечая 200. Один из прошлых прогонов был полностью зелёным, не выкатив ничего — отсюда прямой API-вызов вместо вебхука.

  • Перед вызовом проверяется наличие всех обязательных реквизитов (URL, compose ID, API key) — при отсутствии деплой не запускается молча (:170).
  • Сам HTTP-запрос на запуск ограничен таймаутом 60 секунд (:184).
  • Проверки CI запускаются для всех push и pull request, но Dokploy вызывается только после пайплайна ветки master (:164).
  • Concurrency-группа сериализует все прогоны — разные ветки не грузят production runner одновременно (:11).
(*) cancel-in-progress: false — устаревшие прогоны не отменяются

Новый push не отменяет уже идущий прогон, а встаёт в очередь и ждёт своей стадии deploy — может пройти заметное время между push и реальной выкладкой, если очередь заполнена (:11).

Проверка фактического запуска деплоя​

Health-check по таймеру опрашивает ещё живые старые контейнеры и может зазеленеть при несостоявшейся выкатке — поэтому verify не ждёт фиксированное время, а дожидается смены состояния compose: сначала уход из done, потом возврат в done.

  • Если Dokploy не выходит из done за 5 минут — пайплайн падает (:225).
  • После начала выкладки verify ждёт её завершения не более 30 минут, отдельно обрабатывая статус error (:237).
  • После выкладки витрина и бэкенд проверяются до 30 раз с интервалом 10 секунд (:258).
  • Перед verify-job проверяется, что заданы HEALTH_WEB_URL/HEALTH_BACKEND_URL (:248).

verify: отчёт по клиентским ошибкам​

После health-check в summary попадает отчёт event=client_error из Loki за окно деплоя — это отчёт, не гейт: он не проваливает пайплайн, только показывает свежие ошибки.

  • Запрашивается не более 50 записей, в summary попадают первые 20 (:291).
  • При отсутствии GRAFANA_TOKEN отчёт пропускается без падения пайплайна (:285).
(*) Сбой запроса к Loki маскируется под «всё чисто»

Если сам curl-запрос к Loki падает, ответ подменяется на {} (:292) — summary после этого честно сообщает «ошибок нет», хотя на самом деле отчёт просто не получен. Не принимайте пустой отчёт в summary за подтверждение отсутствия ошибок без проверки, что запрос к Loki вообще прошёл — при подозрении смотрите Loki напрямую, как описано в CLAUDE.md («Проверка в бою»).

Секреты и переменные репозитория​

Секреты: DOKPLOY_API_KEY, GRAFANA_TOKEN. Переменные: DOKPLOY_URL, DOKPLOY_COMPOSE_ID, HEALTH_WEB_URL, HEALTH_BACKEND_URL.

Раннер​

Раннер живёт на рабочей машине разработчика и включается под конкретный прогон (GITEA_TOKEN=<токен> ./infra/ci-runner-local.sh start, после прогона stop). На прод-сервере раннера быть не должно — npm ci и сборка монорепозитория отбирают память у прод-контейнеров и роняют Dokploy (проверено дважды). Пока раннер выключен, job'ы просто ждут в очереди и подхватываются при следующем старте раннера.