Files
han-app/architectory/arch-03-docker-compose-blueprint.md
T
2026-07-10 10:49:58 +03:00

462 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`;
- для `POST /api/v1/dialogs/*/messages` `proxy_read_timeout` должен быть не меньше `MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s`, чтобы nginx не обрывал sync-wait при async file scan;
- применяет 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/degraded режиме, но не обрабатывает `sync_queue` и не выполняет синхронизацию с 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` и `app_settings` — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md); контракты service tokens — в [`arch-02-api-contracts.md`](arch-02-api-contracts.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`; staging/dev может использовать отдельный host |
| 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`): `/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`: `/health/live` проверяет процесс; `/health/ready` проверяет PostgreSQL `han_app`, Redis `/0` и `/1`, доступность JWKS/discovery Keycloak, S3 permissions для presign/promote и readiness `message-safety`;
- `message-safety`: HTTP 200 от `/health/ready` (проверяет PostgreSQL, Redis, workers, read S3-quarantine);
- `bitrix-sync`: `/health/live` проверяет процесс; `/health/ready` проверяет PostgreSQL, доступ к `sync_queue`, worker state и CRM webhook config; при `BITRIX_SYNC_ENABLED=false` ready возвращает degraded/not-ready с причиной `sync_disabled`;
- `bitrix-local-app`: `/health/live` проверяет процесс; `/health/ready` показывает PostgreSQL, OAuth-токены после установки приложения, connector activation и возможность forward в API при включённом `BITRIX_API_FORWARD_URL`;
- `keycloak`: health endpoint Keycloak; readiness — подключение к managed PostgreSQL;
- `redis`: `redis-cli ping`;
Наружу через `nginx` публикуются только health endpoint, которые нужны Bitrix24 install/callback validation или внешнему мониторингу. Internal services (`message-safety`, internal `bitrix-sync`, Redis, otel) проверяются только из Docker/VPC-сети.
## Порядок запуска
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.
### Backup, restore и cleanup
- Managed PostgreSQL должен иметь ежедневные backups и PITR; целевые RPO/RTO для MVP фиксируются в ops runbook до production-запуска.
- S3-data (`attachments`, `documents`) хранит production-файлы; удаление выполняется только через lifecycle, retention или явный audit-backed процесс.
- S3-quarantine очищается периодическим cleanup job: удаляются просроченные объекты без активного `MessageAttachment`/`safety_tasks` или объекты с завершённым deny/failed lifecycle.
- Redis не является единственным хранилищем бизнес-событий; потеря Redis не должна терять сообщения, sync tasks или audit.