Деплой
Развёртывание — 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 — демо-каталог этого магазина. На боевом развёртывании новой
компании его запускать не нужно.
Порядок
- Заполнить
.env(см. Переменные окружения) — обязательно сменить все секреты иADMIN_PASSWORD. IMAGE_TAG=sha-<полный commit SHA> docker compose up -d— образы приходят готовыми из реестра, на сервере окружения ничего не собирается.- Дождаться, пока
migrateиcms-bootstrapзавершатся с кодом 0, аbackendстанет healthy. - Проверить
docker compose psи/health.
Используется 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 | стенд на сервере A | qa-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.
Порядок:
- Дождаться завершения выкатки.
- Пройти проверяемый сценарий на живом стенде.
- Запросить
event=client_errorза период вокруг выкатки и убедиться, что ошибки прекратились именно после времени деплоя.
Если трафика не было, пустой лог — не подтверждение. Проверка фикса — это сравнение: записи были до выкатки и исчезли после. Такого подтверждения не даёт ни сборка, ни ручной осмотр.
Контейнеры в логах называются catalog-main-<hash>-web-1, -backend-1, -postgres-1,
-redis-1, -meilisearch-1, -cms-1, -migrate-1. Хеш меняется при пересоздании стека —
берите актуальный из метки service_name.
Что сейчас не работает
Прежний apps/e2e (Playwright) был полностью нерабочим и удалён. Новый набор под витрину
Next.js разрабатывается по плану в docs/specs/e2e-automation/.