# arch-03. Docker Compose blueprint > Термины (имена бакетов S3, идентификаторы) — в [`arch-00-glossary.md`](arch-00-glossary.md). Контракт Message Safety Service — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md), раздел «api-backend ↔ message-safety». Переменные окружения и настройки — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). ## Назначение Этот документ описывает целевой Docker Compose контур для первой production-like среды. Он не заменяет будущий `docker-compose.yml`, но задает требования, которым он должен соответствовать. Требования к безопасности на уровне приложения и данных — в [`arch-01-system-architecture.md`](arch-01-system-architecture.md), раздел **«Принципы безопасности»**. Настоящий документ описывает только инфраструктурную реализацию этих принципов в compose/nginx: TLS, маршрутизация, сетевые границы, rate limits на edge. Значения переменных окружения — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). Дублировать прикладные требования (JWT, валидация, CORS в API, PII в логах и т.п.) здесь не нужно — они остаются в `arch-01`. ## Единый compose-контур (обязательно) Это зафиксированное архитектурное требование, а не рекомендация. ### Принцип единого входа - Весь backend-контур поднимается **одной командой** `docker compose up -d` из корня репозитория (`backend/`). - Корневой `docker-compose.yml` — единственный источник правды для production-like среды. Отдельных compose-файлов для production-деплоя отдельных сервисов не должно быть. - **Один `nginx`** поднимается из корневого `docker-compose.yml` и является единой публичной точкой входа с маршрутизацией на все сервисы: - `/api/*` → `api-backend` (REST и `WS /api/v1/realtime`; отдельный path `/realtime/*` **не** используется); - `/auth/*` → `keycloak`; - `/bitrix/*` (public: `/bitrix/handler`, `/bitrix/install`, `/bitrix/placement`, `/health/*` для `bitrix-local-app`) → `bitrix-local-app`; - `/bitrix/sync/*` (public: webhook CRM sync для `bitrix-sync`) → `bitrix-sync`; - web-сборка frontend или прокси на dev-сервер; - `/internal/openlines/*`, `/internal/safety/*`, `/internal/sync/*` **не публикуются** наружу — доступны только из внутренней Docker-сети. - Никакой другой `nginx` (ни в контейнере сервиса, ни на хосте) не терминирует внешний HTTPS для backend-контура. Site-конфиг `tohin.ru` на хосте, если используется, должен проксировать весь трафик на корневой `nginx` контейнера, а не на порты отдельных сервисов напрямую. ### Структура compose через `include` Каждый сервис описывается в собственном `docker-compose.yml` внутри папки сервиса и подключается в корневой файл директивой `include`: ```text backend/ docker-compose.yml # корневой: nginx + include сервисов + общие networks/volumes .env nginx/ docker-compose.yml # описание сервиса nginx (или секция в корневом) nginx.conf conf.d/ certs/ .gitkeep api-backend/ docker-compose.yml # описание сервиса api-backend message-safety/ docker-compose.yml # описание сервиса message-safety bitrix-sync/ docker-compose.yml # описание сервиса bitrix-sync bitrix-local-app/ docker-compose.yml # описание сервиса bitrix-local-app keycloak/ docker-compose.yml # описание сервиса keycloak (или секция в корневом) observability/ docker-compose.yml # otel-collector и т.п. ``` Корневой `backend/docker-compose.yml` (принципиальная схема): ```yaml name: han-chat include: - nginx/docker-compose.yml - api-backend/docker-compose.yml - message-safety/docker-compose.yml - bitrix-sync/docker-compose.yml - bitrix-local-app/docker-compose.yml - keycloak/docker-compose.yml - redis/docker-compose.yml - observability/docker-compose.yml networks: public: backend: observability: volumes: redis-data: nginx-certs: ``` ### Правила для сервисных compose-файлов - Сервисный `docker-compose.yml` описывает **только** сервис(ы) своего модуля: образ, build context, `environment` (через `${VAR}` из корневого `.env`), порты (только внутренние, кроме случаев ниже), `depends_on`, healthcheck, подключение к сетям `public`/`backend`/`observability` (объявленным в корневом файле). - Сервисный файл **не объявляет** сети и volumes верхнего уровня — они объявляются в корневом `docker-compose.yml`. Сервис только ссылается на них через `networks:` / `volumes:` (external-стиль не нужен, т.к. `include` объединяет файлы в один проект). - Публикация портов наружу (`ports:`) разрешена **только** для `nginx` (80/443). Все остальные сервисы используют `expose:` для внутренних портов и общаются через Docker-сети. - `bitrix-local-app` не публикует `8080` на хост (даже на `127.0.0.1`) — он доступен `api-backend` и `nginx` через сеть `backend`/`public`. Ранее применявшийся `127.0.0.1:8080:8080` считаем устаревшим; проверки через curl на `127.0.0.1:8080` заменяются на `docker compose exec bitrix-local-app` или прокси через `nginx`. - Каждый сервисный compose-файл должен запускаться и в составе корневого контура, и автономно (`docker compose -f bitrix-local-app/docker-compose.yml up`) для локальной разработки сервиса — при условии, что переменные окружения заданы. Для автономного запуска сервис может объявлять заглушки сетей/volumes, но в составе корневого контура они переопределяются общими. ### Команды разработки ```text docker compose up -d docker compose logs -f nginx docker compose logs -f api-backend docker compose logs -f message-safety docker compose logs -f bitrix-sync docker compose logs -f bitrix-local-app docker compose exec api-backend alembic upgrade head docker compose exec api-backend pytest docker compose exec api-backend ruff check . docker compose exec api-backend ruff format . ``` ## Сервисы ### nginx Reverse proxy и единственная публичная точка входа в Docker Compose контур. Требования: - публикует наружу только `80` и `443` (см. политику HTTP ниже); - принимает внешний HTTPS-трафик; - выполняет TLS termination на reverse proxy; внутренний HTTP между контейнерами — только в закрытой Docker-сети `backend`; - **политика HTTP/HTTPS по доменам** (каноническое правило — [`arch-01-system-architecture.md`](arch-01-system-architecture.md), «Принципы безопасности»): - **веб-домен** (frontend, SPA, статика): `listen 80` допускается **только** для безусловного редиректа `301`/`308` на HTTPS; обработка бизнес-логики по HTTP запрещена; - **API-домен** (если выделен отдельный host, напр. `api.example.ru`): **не** слушает порт `80`; только `listen 443 ssl`; HTTP-запросы к API-домену недоступны; - **единый домен MVP** (напр. `tohin.ru` с путями `/api/*`, `/auth/*`, web): считается веб-доменом; порт `80` — только redirect на HTTPS для всего server block; после редиректа весь пользовательский трафик — HTTPS; - **auth** на том же host, что API (`/auth/*`): следует политике host (redirect-only на :80 или HTTPS-only для выделенного API-host); - **Bitrix callbacks** (`/bitrix/*`, `/bitrix/sync/*`): только HTTPS; порт `80` не обслуживает эти location — только redirect; - маршрутизирует `/api/*` в `api-backend` (включая WebSocket upgrade для `/api/v1/realtime`); - маршрутизирует `/auth/*` в `keycloak` или проксирует отдельный auth-домен; - маршрутизирует публичные `/bitrix/*` endpoint в `bitrix-local-app`; - маршрутизирует `/bitrix/sync/*` webhook endpoint в `bitrix-sync`; - закрывает `/internal/*` (в т.ч. `bitrix-local-app`, `message-safety`, `bitrix-sync` ops) от публичного доступа — только private network Docker/VPC; - **не публикует** `message-safety` наружу; - **production-like / production**: отдаёт **статическую сборку Expo web** из volume или каталога (`/usr/share/nginx/html` или аналог); `index.html` + assets, SPA fallback `try_files $uri /index.html`; - **local dev** (опционально): при `FRONTEND_DEV_PROXY_ENABLED=true` проксирует `/` на Expo dev server (`EXPO_DEV_SERVER_URL`, напр. `http://host.docker.internal:8081`); - передает upstream-сервисам `Host`, `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Request-ID`; - если входящий запрос **без** `X-Request-ID`, nginx **генерирует** UUID и устанавливает заголовок до proxy_pass (I3); - задает разумные `proxy_connect_timeout`, `proxy_read_timeout`, `client_max_body_size`; - применяет edge rate limits для auth, API и download endpoints; - ограничивает частоту соединений и размер тела запроса; - разрешает только TLS 1.2/1.3 и запрещает слабые шифры; - добавляет HSTS и базовые security headers; - скрывает заголовки, раскрывающие внутренние технологии; - кэширует публичные endpoint настроек и контента; - не проксирует наружу managed PostgreSQL, `redis`, `otel-collector` (БД вне compose, в VPC); ### api-backend Python FastAPI backend. Требования: - запускается после доступности managed PostgreSQL, `keycloak`, `redis`; - применяет настройки из `.env`; - отдает `/health/live` и `/health/ready`; - корректно работает за reverse proxy и доверяет proxy headers только от `nginx`; - применяет API-level rate limits с состоянием в Redis; - вызывает message safety pipeline для сообщений до отправки в Open Lines; - вызывает `bitrix-local-app` для отправки сообщений в Open Lines; - принимает forward нормализованных событий оператора от `bitrix-local-app`; - поддерживает realtime endpoint для сообщений оператора; - работает с Selectel S3 для файлов и документов; - экспортирует traces/logs в `otel-collector`; - не хранит состояние внутри контейнера. ### message-safety Отдельный backend-сервис проверки входящих сообщений пользователя. HTTP-контракт — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md), раздел «api-backend ↔ message-safety». Требования: - запускается после доступности managed PostgreSQL (схема `message_safety`), `redis`; - **не публикуется** через `nginx` — доступен только из внутренней Docker-сети; - отдаёт `/health/live` и `/health/ready` (ready проверяет PostgreSQL, Redis, workers, read-доступ к S3-quarantine); - exposing endpoints: `POST /internal/safety/v1/messages/check`, `GET /internal/safety/v1/messages/tasks/{task_id}` (internal Docker network + `X-Service-Token` / `MESSAGE_SAFETY_SERVICE_TOKEN`); - read-only доступ к S3-quarantine (отдельный access key без прав записи); - использует отдельную схему `message_safety` в managed PostgreSQL и отдельный DB-user; - использует Redis (отдельная DB, напр. `redis://redis:6379/2`) для verdict cache и rate limits; - запускает async workers для file scan из S3-quarantine; - экспортирует traces/logs в `otel-collector`; - таймауты: POST check 5 s, GET task 2 s, file scan 60 s (см. [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), переменные `MESSAGE_SAFETY_*`). ### bitrix-sync Python worker/service **двусторонней** синхронизации App DB ↔ Bitrix24 CRM. Требования: - запускается после готовности managed PostgreSQL, `redis`; - читает задачи из `han_app.sync_queue` (заполняется триггерами App DB); - имеет прямой доступ к `han_app` (`BITRIX_SYNC_APP_DATABASE_URL`) и схеме `bitrix_sync`; - выполняет map/create Contact по телефону (интервал `BITRIX_SYNC_CONTACT_MAP_INTERVAL_SEC`, default 60); - push обновлений Contact (интервал `BITRIX_SYNC_CONTACT_UPDATE_INTERVAL_SEC`, default 30); - принимает webhook `POST /bitrix/sync/webhook/contact` от роботов Bitrix24; - при записи в App DB от Bitrix использует GUC `han.sync_suppress=true`; - поддерживает graceful shutdown и rate limiting Bitrix REST; - не блокирует пользовательский API при ошибках Битрикс24; - не участвует в OTP-flow, не создаёт `UserIdentity`/`ClientProfile`; - **не участвует** в hot path чата Open Lines; - включается/отключается флагом **`BITRIX_SYNC_ENABLED`** в `.env` (default `true`): при `false` сервис не стартует или работает в no-op (синхронизация с Bitrix24 CRM не выполняется). ### bitrix-local-app Локальное приложение Bitrix24 и custom connector `han_mobile_app`. Требования: - публикует наружу только `/bitrix/handler`, `/bitrix/install`, `/bitrix/placement`, `/health/live`, `/health/ready`; - принимает `ONAPPINSTALL` и `ONIMCONNECTOR*` события от Bitrix24; - регистрирует и активирует connector `han_mobile_app` для открытой линии 8; - хранит OAuth-токены Bitrix24, inbox событий и `dialog_sessions` в managed PostgreSQL, схема `bitrix_local`; - предоставляет internal API `POST /internal/openlines/v1/messages` и `GET /internal/openlines/v1/dialogs/{external_chat_id}` для api-backend; - защищает internal API через `Authorization: Bearer {BITRIX_INTERNAL_API_TOKEN}`; - forward-ит нормализованные события Open Lines в API, если задан `BITRIX_API_FORWARD_URL`; - не хранит бизнес-данные приложения и не пишет напрямую в App DB. ### Managed PostgreSQL **Во всех средах** (production, production-like, local dev) данные хранятся в **managed PostgreSQL** провайдера. Контейнер PostgreSQL в Docker Compose **не используется** — ни для production, ни для локальной разработки. Прикладные данные, Keycloak, `bitrix-sync`, `bitrix-local-app` и `message-safety` подключаются к одной managed базе по URL из `.env` (`HAN_PG_HOST`, `HAN_PG_PORT`, `HAN_PG_DATABASE` и схемо-специфичные `*_DATABASE_URL`). Требования: - подключение только из приватной сети VPC (VM → managed PostgreSQL); - одна managed база: схемы `han_app`, `bitrix_sync`, `message_safety`, `bitrix_local`, `keycloak`; - отдельные DB-пользователи с доступом только к своей схеме; исключение: `bitrix_sync_user` дополнительно имеет ограниченный GRANT на `han_app` (`sync_queue`, `entity_external_mapping`, tracked columns профиля — детали схемы TBD в спецификации database); - TLS к managed PostgreSQL обязателен; - миграции Alembic выполняются отдельной командой при деплое; - бэкапы и PITR — на стороне провайдера. ### keycloak Identity provider. **Обязателен** в compose-контуре с первого запуска. Требования: - отдельный realm для приложения; - отдельный frontend client с PKCE (обязателен); - confidential backend client — **optional** (не используется в hot path MVP; S2S между сервисами — service tokens); - публичный issuer должен соответствовать HTTPS URL, видимому frontend-приложению; - включены proxy settings для работы за `nginx`; - импорт realm в local/dev; - использует managed PostgreSQL, схема `keycloak` (см. раздел «Managed PostgreSQL» выше); - OTP mock / SMS SPI — см. arch-04; - healthcheck; - взаимодействия — [`arch-02-api-contracts.md`](arch-02-api-contracts.md), «Frontend ↔ Keycloak», и [`arch-01-system-architecture.md`](arch-01-system-architecture.md), «Keycloak». ### redis Кэш, rate limiting, coordination (не единственное хранилище бизнес-событий). Требования: - не использовать как единственное надежное хранилище бизнес-событий; - хранить счетчики API-level rate limits и idempotency keys (`api-backend`); - поддерживать TTL для лимитных и idempotency ключей; - **не** хранить OTP counters для `api-backend` (OTP — зона Keycloak/SPI); - sync_queue хранится в PostgreSQL (`han_app`), Redis может использоваться для wake-up/locking/queue optimization; - разделение DB index (I4): см. arch-04 (`REDIS_URL`, `MESSAGE_SAFETY_REDIS_URL`). ### otel-collector Принимает telemetry от сервисов. Требования: - OTLP HTTP/gRPC receiver; - экспорт traces/logs в stdout или платформенный collector; - единые resource attributes: `service.name`, `deployment.environment`. ## Networks Рекомендуемые сети: - `public`: `nginx`, `keycloak` (для прокси `/auth/*`), frontend static/dev access, внешний HTTPS entrypoint. - `backend`: `api-backend`, `message-safety`, `bitrix-sync`, `bitrix-local-app`, `keycloak`, `redis` (managed PostgreSQL — вне compose, в VPC). - `observability`: `otel-collector` + сервисы, экспортирующие telemetry. Базы данных, Redis, Keycloak internal port и API internal port не должны публиковаться наружу. `message-safety` доступен только внутри сети `backend`. Основной пользовательский путь должен идти через `nginx` и HTTPS. ## Volumes Минимальные volumes (production на одной VM): - `redis-data` (опционально, если нужна персистентность); - certbot / TLS volumes для `nginx`. Данные PostgreSQL **не** хранятся в Docker volumes — только managed PostgreSQL вне compose. ## Переменные окружения Корневой `backend/.env` читается всеми сервисами compose через `${VAR}` в сервисных `docker-compose.yml`. Канонический `.env.example`, service tokens, `app_settings` — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md). ## HTTPS и TLS Соответствует [`arch-01-system-architecture.md`](arch-01-system-architecture.md), «Принципы безопасности» (HTTPS, TLS, HSTS). Инфраструктурная реализация: ### Домены и HTTP | Host | Порт 80 | Порт 443 | Примечание | |---|---|---|---| | Веб-домен (frontend) | только `301`/`308` → HTTPS | HTTPS, бизнес-логика | MVP: `tohin.ru` / `app.example.ru` | | API-домен (если выделен) | **не слушает** | только HTTPS | Post-MVP: `api.example.ru` | | Bitrix callbacks (`/bitrix/*`, `/bitrix/sync/*`) | не обслуживает API; только redirect на том же host | HTTPS | webhook и install URL | Правила: - все внешние пользовательские соединения — **HTTPS**; - HTTP допускается **только** на веб-домене как вход для редиректа на HTTPS; - для **выделенного API-домена** HTTP **не допускается** (нет listener на :80); - при едином домене MVP redirect на :80 применяется ко всему host, включая `/api/*` и `/auth/*`, после редиректа — только HTTPS. ### TLS и заголовки - cookies в web-клиенте: `Secure`, `HttpOnly`, корректный `SameSite`; - OIDC redirect URI в Keycloak — HTTPS; - `KEYCLOAK_PUBLIC_URL`, issuer и frontend auth discovery URL совпадают по схеме, host и path; - backend формирует внешние ссылки с учётом `X-Forwarded-Proto=https`; - HSTS включается в production-like среде **после** проверки доменов и сертификатов; - TLS 1.0/1.1 запрещены; минимум TLS 1.2, предпочтительно TLS 1.3; - слабые шифры запрещены на уровне `nginx`; - `nginx` скрывает `Server`, `X-Powered-By` и аналогичные технологические заголовки; - security headers: `Strict-Transport-Security`, `X-Content-Type-Options`, `Referrer-Policy`, `Content-Security-Policy` для web-приложения; - секретный ключ сертификата не коммитится в репозиторий; - использовать сертификаты доверенного CA; автоматизировать выпуск и продление (Let's Encrypt + reload `nginx`); - закрыть прямой доступ к внутренним портам контейнеров извне. ## Nginx routing для Bitrix24 Local App `nginx` должен поддерживать отдельные server/location rules для `bitrix-local-app`. Рекомендуемая схема: - **веб-домен** (MVP: `tohin.ru` или `app.example.ru`): `/api/*` (REST + WS realtime), `/auth/*`, web frontend; `:80` → redirect HTTPS; `:443` — TLS + маршрутизация; - **выделенный API-домен** (post-MVP, опционально): отдельный `server { listen 443 ssl; ... }` **без** `listen 80`; только `/api/*`; - для `location` WebSocket (`/api/v1/realtime`): `proxy_http_version 1.1`, `Upgrade`/`Connection` headers, увеличенный `proxy_read_timeout`; - домен или path `/bitrix/*` → `bitrix-local-app`; `/bitrix/sync/*` → `bitrix-sync`; - `GET/POST /bitrix/handler` и `GET/POST /bitrix/install` доступны публично для Bitrix24; - `/bitrix/placement` доступен публично как заглушка UI настроек коннектора; - `/health/live` и `/health/ready` для `bitrix-local-app` доступны только там, где это нужно для healthcheck и проверки Bitrix form URL; - `/internal/openlines/v1/*` не публикуется наружу или защищается allowlist/private network плюс `Authorization: Bearer {BITRIX_INTERNAL_API_TOKEN}`; - для `/bitrix/*` callbacks кэширование отключено; - для `/bitrix/*` callbacks включены отдельные rate limits, но они не должны блокировать легитимные webhook-повторы Bitrix24. ## Nginx routing для bitrix-sync (CRM webhook) `nginx` маршрутизирует публичные webhook CRM sync в `bitrix-sync`: - `POST /bitrix/sync/webhook/contact` — исходящий webhook от роботов Bitrix24 при изменении Contact; - проверка `BITRIX_SYNC_WEBHOOK_TOKEN` выполняется в `bitrix-sync`; - кэширование отключено; rate limits не должны блокировать легитимные повторы Bitrix24; - `/internal/sync/v1/*` не публикуется наружу (только internal network + `BITRIX_SYNC_SERVICE_TOKEN`). ## Rate limits и защита от abuse Rate limits должны быть распределены по двум слоям. `nginx`: - ограничивает частоту запросов до попадания в API; - держит отдельные зоны лимитов для `/auth`, `/api`, public endpoints, fallback polling и download endpoints; - ограничивает `client_max_body_size`; - ограничивает загрузку файлов лимитом 5 МБ; `client_max_body_size` должен быть чуть выше бизнес-лимита для учета overhead запроса; - применяет `limit_req` для endpoint авторизации и fallback polling; - для публичных endpoint использует лимит не выше 60 запросов в минуту с одного IP, если настройки не говорят иначе; - возвращает `429` при превышении лимитов; - не должен использоваться для сложных пользовательских правил, завязанных на `user_id`. API: - применяет лимиты после проверки JWT; - считает лимиты по `user_id`, IP, route, dialog id и service client; - хранит быстрые счетчики в Redis; - пишет значимые превышения в audit/App DB; - возвращает `Retry-After`, если клиент может повторить запрос позже. Проверка сообщений на prompt injection и вредоносные действия не должна выполняться в `nginx`: это задача отдельного сервиса `message-safety`, вызываемого из `api-backend` (см. [`arch-02-api-contracts.md`](arch-02-api-contracts.md)). ## WAF WAF можно подключить внешним слоем перед `nginx` без изменения бизнес-кода, если соблюдены требования: - `nginx` и API корректно работают с цепочкой proxy headers и доверяют real IP только от доверенных прокси; - CORS разрешает только доверенные домены; - публичные endpoint имеют rate limits и кэширование даже без WAF; - схема TLS termination согласована с тем, где завершается TLS: WAF/CDN, load balancer или `nginx`; - WAF не должен подменять тело запросов и ответы API без явной необходимости. WAF не заменяет обязательные лимиты, валидацию схем, авторизацию и аудит внутри приложения. ## Публичные endpoint `GET /api/v1/public/app-config` и `GET /api/v1/public/content` являются публичными, поэтому для них обязательны: - `limit_req` на уровне `nginx`, базово 60 запросов в минуту с одного IP; - агрессивное кэширование на уровне `nginx` или CDN; - заголовок `Cache-Control: public, max-age=3600`; - строгая DTO-схема ответа на backend, без сериализации всех строк таблицы настроек; - CORS только для доверенных доменов приложения; - отсутствие секретов, внутренних URL, service tokens и приватных feature flags в ответе. Подробнее — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), разделы «Публичный config endpoint» и «Публичный content endpoint». ## Healthchecks Минимальные проверки: - `nginx`: на веб-домене — `301` с `:80` на HTTPS; на API-домене (если выделен) — `:80` не слушает; `:443` — HTTP 200/301 и успешная TLS handshake; - `api-backend`: HTTP 200 от `/health/ready`; - `message-safety`: HTTP 200 от `/health/ready` (проверяет PostgreSQL, Redis, workers, read S3-quarantine); - `bitrix-sync`: HTTP 200 от `/health/live` и `/health/ready` (ready — PostgreSQL + доступ к `sync_queue`); - `bitrix-local-app`: HTTP 200 от `/health/live`, readiness показывает наличие OAuth-токенов после установки приложения; - `keycloak`: health endpoint Keycloak; readiness — подключение к managed PostgreSQL; - `redis`: `redis-cli ping`; ## Порядок запуска 1. `redis` (managed PostgreSQL должна быть доступна до старта зависимых сервисов). 2. `keycloak`. 3. `otel-collector`. 4. `message-safety`. 5. `api-backend`. 6. `bitrix-local-app`. 7. `bitrix-sync`. 8. `nginx`. `depends_on` не заменяет проверку готовности. Сервисы должны уметь ждать зависимости или корректно завершаться с понятной ошибкой. `api-backend` должен ждать готовности `message-safety` (healthcheck), т.к. отправка сообщения синхронно зависит от `POST /internal/safety/v1/messages/check`. ## Развёртывание на одной VM Production-контур на `tohin.ru`: 1. VM и managed PostgreSQL в одном VPC/кластере провайдера. 2. Managed PostgreSQL без публичного IP; security group разрешает подключение только с VM. 3. `docker compose up -d` на VM поднимает все сервисы кроме БД. 4. Сервисы подключаются к managed PostgreSQL по приватному FQDN/IP. ## Production-замечания ### Frontend (Expo web) | Режим | Поведение nginx | |---|---| | production-like / production | Статика Expo web (`expo export` / EAS web build), `FRONTEND_DEV_PROXY_ENABLED=false` | | local dev | Опционально proxy на Expo dev server, `FRONTEND_DEV_PROXY_ENABLED=true` | Переменные — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), блок «Frontend (nginx)». Docker Compose на одной VM — production-контур первого этапа. Позже при росте нагрузки можно отдельно решить: - вынос Redis в managed cache; - managed object storage; - secret manager; - TLS, reverse proxy или managed ingress; - backup и restore; - централизованный мониторинг; - горизонтальное масштабирование API и worker.