Skip to content

Docker Compose запуск ​

Контейнерная схема поднимает API, notifications worker, MCP и статическую документацию. PostgreSQL и S3-compatible storage не создаются локально: сервисы читают удаленные подключения из .env.

Сервисы ​

СервисНазначениеПорт
appПубличный вход: docs, API и MCP на одном домене8080
backendBackend API внутри compose-сети9000
notifications-workerEmail delivery/retry/outbox processing для уведомленийinternal
docsVitePress + Scalar API Reference внутри compose-сети9002
mcpMCP Streamable HTTP endpoint внутри compose-сети9001
migrateРазовая команда goose migrationsprofile tools
seedРазовая команда seed dataprofile tools

Публичный домен должен вести на первый сервис app, internal port 8080. Это важно для Timeweb App Platform: публичное проксирование Docker Compose настраивается только для первого сервиса в docker-compose.yml. app проксирует / и /scalar/* в docs, /api/* и /health в backend, /mcp в mcp. Основной compose.yaml публикует только container port 8080 у app без фиксированного host-порта, чтобы Caddy на деплой-платформе увидел публичный сервис и при этом Docker выбрал свободный host-порт. Port 80 не используется в ports, потому что Timeweb резервирует его для внутреннего роутинга.

backend, docs и mcp намеренно зависят от app, хотя фактический трафик идет в обратную сторону. Это фиксирует порядок для Timeweb: docker compose config --services должен начинаться с публичного сервиса app, иначе платформа может проксировать домен прямо в backend. Nginx внутри app использует Docker DNS resolver и может стартовать до backend, docs и mcp; healthchecks остаются диагностическими.

У сервисов backend, mcp, migrate и seed разные image tags, хотя они собираются из одного Dockerfile. Это нужно для деплой-платформ, которые собирают compose-сервисы параллельно и не допускают одновременный экспорт нескольких build target в один локальный тег.

Compose использует переменную IMAGE_TAG для имен образов. Если деплой-платформа переиспользует старые локальные образы и в логе нет строк Docker build (#[stage], exporting to image), задайте новый IMAGE_TAG или обновите default tag в compose.yaml и docker-compose.yml. Без .env текущий default tag — deploy-20260609-r3, чтобы Timeweb не поднимал старые :local образы.

Env ​

Локально скопируйте пример и заполните реальные значения:

bash
cp .env.docker.example .env

На сервере .env файл не обязателен. Compose-файл передает переменные из окружения деплой-платформы в контейнеры, поэтому значения можно задавать в UI/секретах платформы.

Минимально нужны:

  • APP_POSTGRES_DSN или POSTGRESQL_HOST, POSTGRESQL_PORT, POSTGRESQL_USER, POSTGRESQL_PASSWORD, POSTGRESQL_DBNAME, POSTGRESQL_SSLMODE;
  • JWT_SECRET, JWT_REFRESH_SECRET;
  • APP_FILE_STORAGE_MODE=s3;
  • S3_ENDPOINT, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET.
  • если фронт открыт на другом домене: APP_CORS_ALLOWED_ORIGINS, например https://staging.pm.rms.group. В compose default для staging уже добавлены https://staging.pm.rms.group и https://staging.crm.rms.group.

Опционально:

  • APP_AUTO_CREATE_PROJECT_KICKOFF_TASK=false — отключает стартовую задачу проекта («Старт проекта: …») на менеджера при создании проекта. По умолчанию включено: её требует сценарий раздела 5 roadmap.

Запуск ​

bash
make up

make up пересобирает локальные образы, применяет миграции и поднимает app, backend, notifications-worker, docs, mcp.

Проверка:

bash
curl http://localhost:8088/health
curl http://localhost:8088/

Локально через compose.local.yaml документация доступна на http://localhost:8088, API healthcheck — http://localhost:8088/health, MCP endpoint — http://localhost:8088/mcp.

Страницы VitePress используют чистые URL без .html. Контейнер документации должен разрешать /concepts/example в сгенерированный файл /concepts/example.html до fallback на /index.html; это настроено в docker/docs/nginx.conf. После изменения конфигурации nginx необходимо пересобрать сервис docs.

Шлюз app (docker/gateway/nginx.conf) пропускает в /api/ тело запроса до 1034 МБ — лимит файла 1 ГБ плюс запас на multipart — и не буферизует его. Без client_max_body_size nginx режет загрузки на 1 МБ и отвечает 413 без CORS-заголовков, поэтому браузер показывает сетевую ошибку. Тест TestGatewayBodyLimitCoversUploadLimit сверяет лимит шлюза с лимитом загрузки сервиса; после правки конфигурации пересобрать сервис app.

Авторизованный администратор может завершить сессию кнопкой «Выйти» в нижней части бокового меню. Кнопка удаляет локальные access/refresh token и сразу возвращает экран входа; сохраненный адрес API остается доступен для следующего входа.

При загрузке страницы сначала показывается нейтральный прелоадер. Форма входа появляется только после неуспешной проверки сохраненной admin-сессии, поэтому авторизованный пользователь не видит мигание формы при обновлении страницы.

MCP для Codex-агента ​

MCP остается read-only Streamable HTTP endpoint на /mcp, но отдает не только OpenAPI. Сервер строит компактный контекст из docs/public/openapi.json, docs/assets/data/contracts.json, docs/assets/data/endpoints.json, docs/assets/data/domain_context.json, markdown-документов и структуры internal/modules.

Основные tools:

  • get_project_context — стек, архитектура, Docker services, правила AGENTS и генераторы docs artifacts.
  • list_domains — канонические домены CRM/RMS.
  • get_domain_context(domain) — docs, packages, endpoints, schemas, migrations и tests по домену.
  • search_project(query, scope) — поиск по docs, OpenAPI, contracts, endpoint inventory и Go filenames.
  • get_api_contract(method, path) — конкретный endpoint с request/response shape и связанными docs.
  • get_change_checklist(domain, change_type) — обязательные шаги для API/schema/business/worker/frontend/RBAC changes.
  • get_docs_health — проверка sidebar links, generated artifacts, registry coverage и module docs.

Основные resources:

  • crm://project/context
  • crm://domains
  • crm://openapi
  • crm://contracts
  • crm://docs/{path}

Миграции и seed ​

Миграции применяются к удаленной базе, указанной в .env:

bash
make migrate
docker compose -f compose.yaml -f compose.local.yaml run --rm migrate status

Seed запускается отдельно:

bash
make seed

Порты ​

Host-порт локального app переопределяется в .env:

env
GATEWAY_PORT=8088

Внутри контейнеров сервисы слушают app:8080, backend:9000, docs:9002 и mcp:9001.

Ubuntu VPS ​

Для отдельного Ubuntu-сервера используйте основной compose-файл вместе с VPS override:

bash
docker compose -f compose.yaml -f compose.vps.yaml up -d --build app backend notifications-worker docs mcp

compose.vps.yaml переопределяет compose-публикацию портов и публикует gateway только на loopback-адресе 127.0.0.1:8088 по умолчанию. Публичный доступ должен идти через системный reverse proxy на 80/443, например Caddy:

caddyfile
crm.example.com {
	reverse_proxy 127.0.0.1:8088
}

Минимальный серверный layout:

  • /opt/crm-rms/app — исходный код и compose-файлы;
  • /opt/crm-rms/app/.env — реальные production-переменные окружения;
  • /etc/caddy/Caddyfile — публичный домен и reverse proxy до 127.0.0.1:8088.

Для первичного деплоя:

bash
cd /opt/crm-rms/app
docker compose -f compose.yaml -f compose.vps.yaml run --rm migrate
docker compose -f compose.yaml -f compose.vps.yaml up -d --build app backend notifications-worker docs mcp
curl http://127.0.0.1:8088/health

После привязки DNS A-записи домена к IP сервера укажите домен в Caddyfile, перезагрузите Caddy и проверьте публичный healthcheck:

bash
systemctl reload caddy
curl https://crm.example.com/health

Загружаем документацию…