Docker Compose запуск
Контейнерная схема поднимает API, notifications worker, MCP и статическую документацию. PostgreSQL и S3-compatible storage не создаются локально: сервисы читают удаленные подключения из .env.
Сервисы
| Сервис | Назначение | Порт |
|---|---|---|
app | Публичный вход: docs, API и MCP на одном домене | 8080 |
backend | Backend API внутри compose-сети | 9000 |
notifications-worker | Email delivery/retry/outbox processing для уведомлений | internal |
docs | VitePress + Scalar API Reference внутри compose-сети | 9002 |
mcp | MCP Streamable HTTP endpoint внутри compose-сети | 9001 |
migrate | Разовая команда goose migrations | profile tools |
seed | Разовая команда seed data | profile 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
Локально скопируйте пример и заполните реальные значения:
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.
Запуск
make upmake up пересобирает локальные образы, применяет миграции и поднимает app, backend, notifications-worker, docs, mcp.
Проверка:
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/contextcrm://domainscrm://openapicrm://contractscrm://docs/{path}
Миграции и seed
Миграции применяются к удаленной базе, указанной в .env:
make migrate
docker compose -f compose.yaml -f compose.local.yaml run --rm migrate statusSeed запускается отдельно:
make seedПорты
Host-порт локального app переопределяется в .env:
GATEWAY_PORT=8088Внутри контейнеров сервисы слушают app:8080, backend:9000, docs:9002 и mcp:9001.
Ubuntu VPS
Для отдельного Ubuntu-сервера используйте основной compose-файл вместе с VPS override:
docker compose -f compose.yaml -f compose.vps.yaml up -d --build app backend notifications-worker docs mcpcompose.vps.yaml переопределяет compose-публикацию портов и публикует gateway только на loopback-адресе 127.0.0.1:8088 по умолчанию. Публичный доступ должен идти через системный reverse proxy на 80/443, например Caddy:
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.
Для первичного деплоя:
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:
systemctl reload caddy
curl https://crm.example.com/health