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

Деплой

Развёртывание — Docker Compose: четыре собираемых образа (backend, web, cms, docs) плюс postgres, redis, meilisearch, minio. Устройство самого compose-файла — Docker Compose.

Приложение настраивает себя само​

Цель — один и тот же образ разворачивается на новом сервере для новой компании без ручных шагов, специфичных для конкретного магазина:

  • Миграции прогоняет одноразовый сервис migrate (medusa db:migrate) до старта backend. Рантайм-образ схему не мигрирует сам, поэтому на свежей БД без этого шага backend падает на каждом module loader с «relation … does not exist».

  • Администратор создаётся сервисом backend-init (scripts/init-admin.sh → medusa user) из ADMIN_EMAIL/ADMIN_PASSWORD. Повторный запуск безопасен, дубля не будет.

  • Sales channel по умолчанию и publishable key создаёт сам Medusa core (createDefaultsWorkflow) при каждом старте, если их ещё нет. Отдельный сид не нужен.

  • Витрина забирает publishable key в рантайме через GET /catalog/config — на первом деплое ключа ещё не существует на момент сборки.

  • CMS настраивает одноразовый сервис cms-bootstrap: после health-check Strapi он создаёт read-токен, атомарно сохраняет его с правами только владельца в именованном томе и создаёт с публикацией начальные Homepage и Site settings. backend получает том только для чтения и ждёт успешного завершения сервиса. Если главная или настройки уже опубликованы, bootstrap ничего не меняет. Если запись лежит только черновиком, bootstrap останавливает деплой: опубликовать её за человека он не может (middleware публикации переписал бы sections, а бэкенда в этот момент ещё нет), а бэкенд читает только опубликованные версии и получил бы 404. Такой черновик нужно опубликовать в админке CMS и выкатить снова. Первое имя компании — COMPANY_NAME либо «Новый магазин».

  • Ручной STRAPI_READ_TOKEN остаётся совместимым переопределением: если он задан, backend использует его вместо файла. Сам токен bootstrap никогда не пишет в лог.

  • Откат на релиз до cms-bootstrap требует ручного шага. Старый backend читает только переменную STRAPI_READ_TOKEN и про файл в томе не знает: откатите один образ backend, не трогая окружение, — и CMS-снимки начнут падать, хотя токен лежит в томе. Перед откатом перенесите токен в окружение Dokploy:

    docker exec <проект>-backend-1 cat /run/cms-bootstrap/strapi-read-token

    и задайте выведенное значение как STRAPI_READ_TOKEN. Откат вперёд этого не требует: заданная переменная остаётся приоритетной и для нового backend.

npm run backend:seed — демо-каталог этого магазина. На боевом развёртывании новой компании его запускать не нужно.

Порядок​

  1. Заполнить .env (см. Переменные окружения) — обязательно сменить все секреты и ADMIN_PASSWORD.
  2. IMAGE_TAG=sha-<полный commit SHA> docker compose up -d — образы приходят готовыми из реестра, на сервере окружения ничего не собирается.
  3. Дождаться, пока migrate и cms-bootstrap завершатся с кодом 0, а backend станет healthy.
  4. Проверить docker compose ps и /health.
Сборка образа backend

Используется BuildKit-кэш npm (--mount=type=cache). Не запускайте несколько docker build параллельно — они дерутся за кэш. Детали — docs/specs/_archive/auto-paint-store/stack.md.

Перед публикацией наружу

Появился TLS-прокси — переключите SESSION_COOKIE_SECURE на true, иначе вход покупателя молча сломается. И смените пароль администратора: значение из репозитория паролем не является.

Три окружения​

ОкружениеЧто этоАдреса
prodбоевой магазинmirkrasok.termitsdigital.ru, api.mirkrasok.termitsdigital.ru, cdn.mirkrasok.termitsdigital.ru, cms.mirkrasok.termitsdigital.ru, docs.mirkrasok.termitsdigital.ru; прежние псевдонимы: mirkrasok-api.termitsdigital.ru, mirkrasok-cdn.termitsdigital.ru, mirkrasok-cms.termitsdigital.ru
qa-aстенд на сервере Aqa-a.mirkrasok.termitsdigital.ru, api-qa-a.mirkrasok.termitsdigital.ru, cdn-qa-a.mirkrasok.termitsdigital.ru, cms-qa-a.mirkrasok.termitsdigital.ru, docs-qa-a.mirkrasok.termitsdigital.ru
qa-bстенд на сервере Бqa-b.mirkrasok.termitsdigital.ru, api-qa-b.mirkrasok.termitsdigital.ru, cdn-qa-b.mirkrasok.termitsdigital.ru, cms-qa-b.mirkrasok.termitsdigital.ru, docs-qa-b.mirkrasok.termitsdigital.ru

Выкладка ставит окружению точную версию своего запуска: перед вызовом Dokploy она записывает в переменные окружения IMAGE_TAG=sha-<коммит этого запуска> и проверяет, что значение записалось. Поэтому кнопка в старом запуске выкатывает его версию, а не последнюю собранную, и docker, пересоздавая контейнер сам, поднимет ровно ту же. Теги prod/qa-a/qa-b в реестре остаются указателем «что сейчас выложено», но стек по ним не поднимается — и переводятся они последним шагом, уже после того как health подтвердил выкладку.

Каждому окружению в GitLab отвечают переменные со своим environment_scope: DOKPLOY_COMPOSE_ID, HEALTH_WEB_URL, HEALTH_BACKEND_URL. Выкладка — ручная кнопка deploy:prod, deploy:qa-a или deploy:qa-b на зелёном запуске; одно окружение — одна resource_group, поэтому выкладки prod и qa-a на сервер A идут по очереди.

У каждого стенда есть свои DNS-имена. Traefik раздаёт приложения по именам qa-a/qa-b и их api, cdn, cms и docs поддоменам; бэкенд доступен по соответствующему api-qa-a или api-qa-b имени.

Проверка после выкатки: логи обязательны​

Зелёный статус деплоя и визуальный осмотр страницы не считаются проверкой. Логи ловят то, что не видно глазом: перехваченные error boundary исключения, сбои SSR, ошибки соседних сервисов.

Витрина логирует клиентские ошибки структурно (apps/web/app/api/client-errors/route.ts + ClientErrorListener):

[storefront] level=ERROR event=client_error method=… path=… message=… stack=…

Устройство пайплайна клиентских ошибок​

lib/client-error-reporter.ts, app/api/client-errors/route.ts:

  • Обезличивание перед отправкой: email, телефоны, токены, cookies и query-параметры вычищаются из сообщения и стека перед тем, как ошибка уйдёт в лог — в Loki не попадают персональные данные покупателя.
  • Дедупликация одинаковых отпечатков ошибки на 30 секунд, карта отпечатков ограничена 100 записями — при переполнении старейшие удаляются даже до истечения их 30-секундного окна (:23).
  • page_request (навигации) обрабатывается отдельно от JS-ошибок: prefetch-запросы и служебные пути исключаются, чтобы не засорять лог технической навигацией Next.js.
  • Диагностические поля перед отправкой обрезаются: message — до 500 символов, stack — до 2000 символов и первых четырёх строк, path — до 300 символов.
(*) Дедупликация и reportInFlight — ограничения на уровне вкладки браузера, не сервера

client-error-reporter.ts — клиентский модуль ("use client"): и дедупликация отпечатков, и флаг reportInFlight живут в JS-контексте конкретной вкладки браузера конкретного покупателя. Пока идёт отправка одного отчёта из этой вкладки, новые ошибки той же вкладки отбрасываются (:40) — но это не мешает другим вкладкам или другим покупателям отправлять свои отчёты одновременно.

Формат тела запроса (route.ts): только Content-Type: application/json — иначе 415; обязательные строковые поля pathname, method, message; необязательное строковое stack. Отклонения: превышение размера тела (16 КиБ) — 413; невалидный JSON или не тот набор полей — 400; исчерпан лимит частоты — 429. Успешная запись отвечает 204 без тела.

(*) Endpoint ограничен глобально на весь серверный процесс

В отличие от предыдущего — лимит 30 сообщений в минуту и 16 КиБ на запрос в /api/client-errors (acceptedAt-массив в route.ts) действительно общий на весь процесс Next.js, а не на посетителя. Один источник шума (например, зациклившаяся ошибка у одного покупателя) способен исчерпать лимит и на минуту заблокировать приём отчётов от всех остальных посетителей. Не считайте отсутствие записи в Loki доказательством, что ошибки не было — см. также предупреждение про Loki-отчёт CI в CI/CD.

Порядок:

  1. Дождаться завершения выкатки.
  2. Пройти проверяемый сценарий на живом стенде.
  3. Запросить event=client_error за период вокруг выкатки и убедиться, что ошибки прекратились именно после времени деплоя.
Отсутствие ошибок за минуту ничего не доказывает

Если трафика не было, пустой лог — не подтверждение. Проверка фикса — это сравнение: записи были до выкатки и исчезли после. Такого подтверждения не даёт ни сборка, ни ручной осмотр.

Контейнеры в логах называются catalog-main-<hash>-web-1, -backend-1, -postgres-1, -redis-1, -meilisearch-1, -cms-1, -migrate-1. Хеш меняется при пересоздании стека — берите актуальный из метки service_name.

Что сейчас не работает​

E2E-набор переписывается заново

Прежний apps/e2e (Playwright) был полностью нерабочим и удалён. Новый набор под витрину Next.js разрабатывается по плану в docs/specs/e2e-automation/.