- docs/dev: добавлены 10 API-разделов (Webhooks, Catalog, Departments, Rooms, Clubs Admin, Licenses, Reports, Integration, Audit Logs), TOTP-эндпоинты, разделы BullMQ и Rate Limiting, обновлено содержание - docs/devops: добавлены BullMQ очереди (Redis 3.2), PM2 (7.5), Rate Limiting (9.7) - docs/user: убрано «в разработке» для панелей клуба и суперадмина, добавлена инструкция 2FA, описаны CRUD-диалоги, CSV-экспорт аудита Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FitCRM -- Руководство по развертыванию и DevOps
Полное руководство по развертыванию, настройке инфраструктуры и эксплуатации CRM-системы для фитнес-клубов FitCRM.
Содержание
- Требования к окружению
- Быстрый старт
- Docker-сервисы
- Настройка базы данных
- Конфигурация окружения (.env)
- Запуск в режиме разработки
- Сборка для production
- CI/CD пайплайн
- SSL/TLS и безопасность
- Резервное копирование и восстановление
- Масштабирование
- Устранение неполадок
1. Требования к окружению
Обязательные зависимости
| Компонент | Минимальная версия | Рекомендуемая версия | Примечание |
|---|---|---|---|
| Node.js | 22.0.0 | 22 LTS | Указано в engines в package.json |
| pnpm | 9.14.0 | 9.x | Указано в packageManager |
| Docker | 24.0 | 27.x | Для запуска инфраструктурных сервисов |
| Docker Compose | 2.20 | 2.30+ | Compose V2 (встроен в Docker Desktop) |
| Git | 2.39 | 2.43+ | Для клонирования и worktrees |
Системные требования
Для разработки (development):
- CPU: 4 ядра
- RAM: 8 ГБ (PostgreSQL + Redis + coturn + Node.js)
- Диск: 10 ГБ свободного места
Для production (один сервер):
- CPU: 8 ядер
- RAM: 16 ГБ
- Диск: 50 ГБ SSD (данные PostgreSQL, логи, файлы S3)
- Сеть: фиксированный IP-адрес (обязателен для STUN/TURN)
Установка зависимостей
Node.js 22 LTS (через nvm):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
node --version # v22.x.x
pnpm 9:
corepack enable
corepack prepare pnpm@9.14.0 --activate
pnpm --version # 9.14.0
Docker и Docker Compose:
# Ubuntu/Debian
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Проверка
docker --version
docker compose version
2. Быстрый старт
Пошаговая инструкция от клонирования репозитория до работающей системы.
Шаг 1. Клонирование репозитория
git clone git@github.com:<org>/fitcrm.git
cd fitcrm
Шаг 2. Настройка переменных окружения
cp .env.example .env
Откройте файл .env и задайте значения для обязательных переменных (подробности -- в разделе 5. Конфигурация окружения):
# Минимум для локальной разработки:
POSTGRES_PASSWORD=my_secure_password_123
REDIS_PASSWORD=my_redis_password_456
TURN_SECRET=my_turn_secret_789
JWT_SECRET=my_jwt_secret_at_least_32_characters_long
JWT_REFRESH_SECRET=my_jwt_refresh_secret_at_least_32_chars
Шаг 3. Запуск инфраструктуры через Docker Compose
docker compose -f infra/docker/docker-compose.yml up -d
Дождитесь готовности сервисов (healthcheck):
docker compose -f infra/docker/docker-compose.yml ps
Все три сервиса должны показывать статус healthy или running.
Шаг 4. Установка зависимостей
pnpm install
Шаг 5. Настройка базы данных
# Генерация Prisma-клиента
pnpm --filter api prisma:generate
# Применение миграций (создание таблиц)
pnpm --filter api prisma:migrate
# Заполнение начальными данными (опционально)
pnpm --filter api prisma:seed
Шаг 6. Запуск в режиме разработки
# Все приложения одновременно (через Turborepo)
pnpm dev
# Или отдельные сервисы:
pnpm --filter api dev # Backend API (порт 3000)
pnpm --filter web-admin dev # Веб-рецепция (порт 3001)
pnpm --filter web-club-admin dev # Админка клуба (порт 3002)
pnpm --filter web-platform-admin dev # Суперадмин (порт 3003)
Шаг 7. Проверка работоспособности
# API
curl http://localhost:3000/health
# Или откройте в браузере:
# http://localhost:3001 - Веб-рецепция
# http://localhost:3002 - Админка клуба
# http://localhost:3003 - Суперадминистратор
3. Docker-сервисы
Обзор архитектуры
Инфраструктура FitCRM состоит из трех сервисов, объединенных в сеть fitcrm-network:
fitcrm-network (bridge)
========================
| | |
+---------+ +-------+ +---------+
|PostgreSQL| | Redis | | coturn |
| :5432 | | :6379 | | :3478 |
+---------+ +-------+ | :5349 |
+---------+
Файлы конфигурации
| Файл | Назначение |
|---|---|
infra/docker/docker-compose.yml |
Основной Compose-файл (development) |
infra/docker/docker-compose.prod.yml |
Production override (лимиты ресурсов, сборка приложений) |
infra/docker/Dockerfile.api |
Multi-stage сборка NestJS API |
infra/docker/Dockerfile.web |
Multi-stage сборка web-admin (Next.js) |
infra/docker/Dockerfile.club-admin |
Multi-stage сборка web-club-admin (Next.js) |
infra/docker/Dockerfile.platform-admin |
Multi-stage сборка web-platform-admin (Next.js) |
infra/stun-turn/turnserver.conf |
Конфигурация coturn STUN/TURN сервера |
3.1. PostgreSQL 17
# Образ: postgres:17-alpine
# Контейнер: fitcrm-postgres
# Порт: 5432 (настраивается через POSTGRES_PORT)
# Volume: postgres_data:/var/lib/postgresql/data
Healthcheck: pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} каждые 10 секунд, 5 попыток.
Особенности:
- Используется с Row-Level Security (RLS) для multi-tenancy изоляции клубов
- Схема содержит 34 таблицы и 12 перечислений (enums)
- Рекомендуемые параметры для production -- см. раздел Масштабирование
3.2. Redis 8
# Образ: redis:8-alpine
# Контейнер: fitcrm-redis
# Порт: 6379 (настраивается через REDIS_PORT)
# Volume: redis_data:/data
# Режим: appendonly yes (AOF-персистенция)
Healthcheck: redis-cli -a ${REDIS_PASSWORD} ping каждые 10 секунд, 5 попыток.
Использование в FitCRM:
- Кэширование данных (сессии, частые запросы)
- Очереди задач BullMQ (push-уведомления, синхронизация 1С, webhooks)
- Rate limiting (скользящее окно per-club)
- Хранение текущего состояния SIP-сессий
BullMQ очереди:
Три именованных очереди зарегистрированы в QueueModule:
| Очередь | Назначение | Retry-стратегия |
|---|---|---|
webhook-delivery |
Доставка webhook-событий (HMAC-SHA256) | 3 попытки: 5с → 30с → 5мин |
push-notification |
Отправка push через FCM/APNs | 3 попытки с экспоненциальной задержкой |
sync-1c |
Синхронизация с 1С:Фитнес | 3 попытки с экспоненциальной задержкой |
Мониторинг очередей:
# Проверка количества задач в очередях
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" KEYS "bull:*"
# Просмотр задач в конкретной очереди
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" LLEN "bull:webhook-delivery:wait"
3.3. coturn (STUN/TURN)
# Образ: coturn/coturn:latest
# Контейнер: fitcrm-coturn
# Порты:
# 3478/udp, 3478/tcp - STUN/TURN основной
# 5349/udp, 5349/tcp - STUN/TURN over TLS
# 49152-65535/udp - Media relay (RTP/SRTP)
Назначение: NAT traversal для встроенного SIP-клиента. Обеспечивает прохождение VoIP-звонков через NAT/файрволлы.
Конфигурация (infra/stun-turn/turnserver.conf):
- Аутентификация:
use-auth-secret(shared secret передается из env) - Квоты: 12 аллокаций на пользователя, 1200 суммарно
- Безопасность: запрещены relay на приватные подсети (10.x, 172.16-31.x, 192.168.x)
- Время жизни канала: 600 секунд
- Время жизни permissions: 300 секунд
Важно: Для production coturn требует публичного IP-адреса. Добавьте в turnserver.conf:
external-ip=<PUBLIC_IP>/<PRIVATE_IP>
Управление Docker-сервисами
# Запуск
docker compose -f infra/docker/docker-compose.yml up -d
# Просмотр статуса
docker compose -f infra/docker/docker-compose.yml ps
# Логи конкретного сервиса
docker compose -f infra/docker/docker-compose.yml logs -f postgres
docker compose -f infra/docker/docker-compose.yml logs -f redis
docker compose -f infra/docker/docker-compose.yml logs -f coturn
# Остановка
docker compose -f infra/docker/docker-compose.yml down
# Остановка с удалением данных (ОСТОРОЖНО!)
docker compose -f infra/docker/docker-compose.yml down -v
4. Настройка базы данных
4.1. Prisma ORM
FitCRM использует Prisma 6 как ORM. Схема БД расположена в apps/api/prisma/schema.prisma.
Основные команды:
# Генерация Prisma-клиента (TypeScript типы для работы с БД)
pnpm --filter api prisma:generate
# Применение миграций к БД
pnpm --filter api prisma:migrate
# Отправка схемы напрямую в БД (без миграции, для прототипирования)
# Используется в CI для тестов
npx prisma db push --skip-generate
# Заполнение начальными данными
pnpm --filter api prisma:seed
# Открытие визуального интерфейса Prisma Studio
pnpm --filter api prisma:studio
4.2. Row-Level Security (RLS)
FitCRM использует PostgreSQL RLS для изоляции данных клубов (multi-tenancy). Каждая таблица с данными клуба содержит столбец club_id. RLS-политики применяются автоматически через TenantMiddleware в NestJS.
Принцип работы:
- Пользователь авторизуется -- из JWT-токена извлекается
clubId - Middleware выполняет
SET app.club_id = <clubId>для текущего соединения - RLS-политика PostgreSQL автоматически фильтрует данные:
ALTER TABLE clients ENABLE ROW LEVEL SECURITY;
CREATE POLICY club_isolation ON clients
USING (club_id = current_setting('app.club_id')::uuid);
Важно: Никогда не отключайте RLS в production. Для административных операций суперадминистратора используется отдельная роль PostgreSQL с BYPASSRLS.
4.3. Миграции в production
# Создание новой миграции после изменения schema.prisma
cd apps/api
npx prisma migrate dev --name описание_изменения
# Применение миграций в production
npx prisma migrate deploy
Рекомендации:
- Всегда проверяйте SQL миграций перед применением в production (
apps/api/prisma/migrations/) - Делайте бэкап БД перед каждой миграцией (см. раздел 10. Резервное копирование)
- Не используйте
prisma db pushв production
4.4. Подключение к БД напрямую
# Через Docker
docker exec -it fitcrm-postgres psql -U fitcrm -d fitcrm
# Из хоста (требуется psql)
psql postgresql://fitcrm:your_password@localhost:5432/fitcrm
Полезные SQL-запросы для диагностики:
-- Проверка RLS-политик
SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual
FROM pg_policies ORDER BY tablename;
-- Размер таблиц
SELECT relname AS table, pg_size_pretty(pg_total_relation_size(relid)) AS size
FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC;
-- Активные соединения
SELECT count(*) FROM pg_stat_activity WHERE datname = 'fitcrm';
-- Долгие запросы (более 5 секунд)
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds'
AND state != 'idle';
5. Конфигурация окружения (.env)
Полный список переменных
Скопируйте .env.example в .env и заполните обязательные значения.
PostgreSQL
| Переменная | По умолчанию | Обязательна | Описание |
|---|---|---|---|
POSTGRES_USER |
fitcrm |
Нет | Имя пользователя PostgreSQL |
POSTGRES_PASSWORD |
-- | Да | Пароль PostgreSQL |
POSTGRES_DB |
fitcrm |
Нет | Имя базы данных |
POSTGRES_HOST |
localhost |
Нет | Хост PostgreSQL (postgres в Docker-сети) |
POSTGRES_PORT |
5432 |
Нет | Порт PostgreSQL |
DATABASE_URL |
-- | Да | Полный URL подключения Prisma |
Формат DATABASE_URL:
postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@<POSTGRES_HOST>:<POSTGRES_PORT>/<POSTGRES_DB>?schema=public
Redis
| Переменная | По умолчанию | Обязательна | Описание |
|---|---|---|---|
REDIS_HOST |
localhost |
Нет | Хост Redis (redis в Docker-сети) |
REDIS_PORT |
6379 |
Нет | Порт Redis |
REDIS_PASSWORD |
-- | Да | Пароль Redis |
STUN/TURN (coturn)
| Переменная | По умолчанию | Обязательна | Описание |
|---|---|---|---|
TURN_REALM |
fitcrm.local |
Нет | Realm для STUN/TURN сервера |
TURN_SECRET |
-- | Да | Shared secret для аутентификации TURN |
JWT-аутентификация
| Переменная | По умолчанию | Обязательна | Описание |
|---|---|---|---|
JWT_SECRET |
-- | Да | Секрет для подписи access-токенов (мин. 32 символа) |
JWT_REFRESH_SECRET |
-- | Да | Секрет для подписи refresh-токенов (мин. 32 символа) |
JWT_EXPIRES_IN |
15m |
Нет | Время жизни access-токена |
JWT_REFRESH_EXPIRES_IN |
30d |
Нет | Время жизни refresh-токена |
API-сервер
| Переменная | По умолчанию | Обязательна | Описание |
|---|---|---|---|
PORT |
3000 |
Нет | Порт API-сервера |
NODE_ENV |
development |
Нет | Окружение: development, staging, production |
S3-хранилище (опционально)
| Переменная | По умолчанию | Обязательна | Описание |
|---|---|---|---|
S3_ENDPOINT |
-- | Нет | URL S3-совместимого хранилища (MinIO, AWS, Yandex Cloud) |
S3_BUCKET |
fitcrm |
Нет | Имя бакета |
S3_ACCESS_KEY |
-- | Нет | Access Key ID |
S3_SECRET_KEY |
-- | Нет | Secret Access Key |
S3_REGION |
us-east-1 |
Нет | Регион |
Пример готового .env для разработки
# PostgreSQL
POSTGRES_USER=fitcrm
POSTGRES_PASSWORD=dev_password_123
POSTGRES_DB=fitcrm
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
DATABASE_URL=postgresql://fitcrm:dev_password_123@localhost:5432/fitcrm?schema=public
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=redis_dev_password_456
# STUN/TURN
TURN_REALM=fitcrm.local
TURN_SECRET=turn_dev_secret_789
# JWT
JWT_SECRET=dev_jwt_secret_must_be_at_least_32_chars
JWT_REFRESH_SECRET=dev_jwt_refresh_must_be_at_least_32_chars
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=30d
# API
PORT=3000
NODE_ENV=development
Генерация безопасных секретов
# JWT_SECRET и JWT_REFRESH_SECRET (64 символа)
openssl rand -base64 48
# REDIS_PASSWORD
openssl rand -base64 32
# POSTGRES_PASSWORD
openssl rand -base64 24
# TURN_SECRET
openssl rand -hex 32
Различия по окружениям
| Параметр | Development | Staging | Production |
|---|---|---|---|
NODE_ENV |
development |
staging |
production |
JWT_EXPIRES_IN |
15m |
15m |
15m |
POSTGRES_HOST |
localhost |
postgres |
postgres или managed DB |
REDIS_HOST |
localhost |
redis |
redis или managed Redis |
| Порты наружу | Все открыты | Только API | Только через reverse proxy |
6. Запуск в режиме разработки
Полный запуск (все сервисы)
# 1. Инфраструктура
docker compose -f infra/docker/docker-compose.yml up -d
# 2. Зависимости
pnpm install
# 3. БД
pnpm --filter api prisma:generate
pnpm --filter api prisma:migrate
pnpm --filter api prisma:seed # опционально
# 4. Все приложения
pnpm dev
Turborepo запустит все workspace-приложения параллельно. Приложения будут доступны на:
| Приложение | URL | Описание |
|---|---|---|
| API | http://localhost:3000 | NestJS Backend (REST API) |
| web-admin | http://localhost:3001 | Рецепция + аналитика |
| web-club-admin | http://localhost:3002 | Панель администратора клуба |
| web-platform-admin | http://localhost:3003 | Панель суперадминистратора |
Запуск отдельных приложений
# Только Backend API (NestJS watch mode)
pnpm --filter api dev
# Только веб-рецепция
pnpm --filter web-admin dev
# Только админка клуба
pnpm --filter web-club-admin dev
# Только суперадмин-панель
pnpm --filter web-platform-admin dev
Мобильное приложение (React Native)
# Запуск Metro bundler
pnpm --filter mobile start
# iOS (требуется macOS + Xcode)
pnpm --filter mobile ios
# Android (требуется Android Studio + SDK)
pnpm --filter mobile android
Полезные команды для разработки
# Линтинг всего проекта
pnpm lint
# Форматирование кода (Prettier)
pnpm format
# Запуск тестов
pnpm test
# Тесты только backend
pnpm --filter api test
# E2E тесты backend
pnpm --filter api test:e2e
# Prisma Studio (визуальный редактор БД)
pnpm --filter api prisma:studio
Горячая перезагрузка
- NestJS API: автоматическая перезагрузка при изменении
.tsфайлов (watch mode) - Next.js приложения: Fast Refresh при изменении компонентов
- React Native: Metro hot reload
7. Сборка для production
7.1. Сборка через Turborepo
# Сборка всех приложений и пакетов
pnpm build
Turborepo выполнит сборку в правильном порядке с учетом зависимостей между workspace-пакетами:
packages/shared-types-- общие TypeScript типыpackages/validators-- Zod-схемы валидацииpackages/api-client-- сгенерированный API-клиентapps/api-- NestJS backendapps/web-admin,apps/web-club-admin,apps/web-platform-admin-- Next.js приложения (параллельно)
7.2. Docker-образы
Каждое приложение имеет отдельный Dockerfile с multi-stage сборкой для минимизации размера образа.
Сборка всех образов:
# Из корня проекта
docker build -f infra/docker/Dockerfile.api -t fitcrm-api:latest .
docker build -f infra/docker/Dockerfile.web -t fitcrm-web-admin:latest .
docker build -f infra/docker/Dockerfile.club-admin -t fitcrm-web-club-admin:latest .
docker build -f infra/docker/Dockerfile.platform-admin -t fitcrm-web-platform-admin:latest .
Структура Dockerfile (на примере API):
| Стадия | Базовый образ | Назначение |
|---|---|---|
deps |
node:22-alpine |
Установка зависимостей (pnpm install --frozen-lockfile) |
builder |
node:22-alpine |
Сборка TypeScript, генерация Prisma-клиента |
runner |
node:22-alpine |
Финальный образ: только dist + node_modules + prisma |
Особенности:
- Все образы запускаются от непривилегированного пользователя (
nestjs/nextjs, UID 1001) - Next.js приложения используют standalone output для минимального размера
- Prisma schema и миграции копируются в production образ API для runtime-миграций
7.3. Production Docker Compose
Файл infra/docker/docker-compose.prod.yml расширяет базовый docker-compose.yml и добавляет:
- Сборку и запуск приложений (api, web-admin, web-club-admin, web-platform-admin)
- Лимиты ресурсов для каждого контейнера
- Закрытые порты инфраструктуры (PostgreSQL и Redis не публикуют порты наружу)
restart: alwaysвместоunless-stopped
Ресурсные лимиты (production):
| Сервис | CPU (лимит) | RAM (лимит) | CPU (резерв) | RAM (резерв) |
|---|---|---|---|---|
| PostgreSQL | 2.0 | 2 ГБ | 0.5 | 512 МБ |
| Redis | 1.0 | 1 ГБ | 0.25 | 256 МБ |
| coturn | 1.0 | 512 МБ | 0.25 | 128 МБ |
| API | 2.0 | 2 ГБ | 0.5 | 512 МБ |
| web-admin | 1.0 | 1 ГБ | 0.25 | 256 МБ |
| web-club-admin | 1.0 | 1 ГБ | 0.25 | 256 МБ |
| web-platform-admin | 1.0 | 1 ГБ | 0.25 | 256 МБ |
Запуск production:
# Сборка и запуск
docker compose -f infra/docker/docker-compose.yml -f infra/docker/docker-compose.prod.yml up -d --build
# Применение миграций после запуска
docker exec fitcrm-api npx prisma migrate deploy
7.4. Reverse proxy
В production все приложения слушают только внутреннюю Docker-сеть (через expose, а не ports). Доступ извне предоставляется через reverse proxy (Traefik или Nginx).
Пример конфигурации Nginx:
# /etc/nginx/sites-available/fitcrm
upstream fitcrm-api {
server 127.0.0.1:3000;
}
upstream fitcrm-web-admin {
server 127.0.0.1:3001;
}
upstream fitcrm-web-club-admin {
server 127.0.0.1:3002;
}
upstream fitcrm-web-platform-admin {
server 127.0.0.1:3003;
}
# API
server {
listen 443 ssl http2;
server_name api.fitcrm.example.com;
ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;
location / {
proxy_pass http://fitcrm-api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Рецепция
server {
listen 443 ssl http2;
server_name reception.fitcrm.example.com;
ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;
location / {
proxy_pass http://fitcrm-web-admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Админка клуба
server {
listen 443 ssl http2;
server_name club.fitcrm.example.com;
ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;
location / {
proxy_pass http://fitcrm-web-club-admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Суперадмин
server {
listen 443 ssl http2;
server_name admin.fitcrm.example.com;
ssl_certificate /etc/letsencrypt/live/fitcrm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fitcrm.example.com/privkey.pem;
location / {
proxy_pass http://fitcrm-web-platform-admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
7.5. PM2 (альтернатива Docker)
Для развертывания без Docker можно использовать PM2 -- менеджер процессов для Node.js.
Установка PM2:
npm install -g pm2
Файл ecosystem.config.js расположен в корне проекта:
module.exports = {
apps: [
{
name: 'fitcrm-api',
script: 'apps/api/dist/main.js',
instances: 'max',
exec_mode: 'cluster',
env: { NODE_ENV: 'production', PORT: 3000 },
},
{
name: 'fitcrm-web-admin',
script: 'node_modules/.bin/next',
args: 'start apps/web-admin',
env: { NODE_ENV: 'production', PORT: 3001 },
},
{
name: 'fitcrm-web-club-admin',
script: 'node_modules/.bin/next',
args: 'start apps/web-club-admin',
env: { NODE_ENV: 'production', PORT: 3002 },
},
{
name: 'fitcrm-web-platform-admin',
script: 'node_modules/.bin/next',
args: 'start apps/web-platform-admin',
env: { NODE_ENV: 'production', PORT: 3003 },
},
],
};
Команды PM2:
# Запуск всех приложений
pm2 start ecosystem.config.js
# Просмотр статуса
pm2 status
# Логи
pm2 logs fitcrm-api
# Перезапуск
pm2 reload all
# Автозапуск при перезагрузке сервера
pm2 startup
pm2 save
8. CI/CD пайплайн
Обзор
FitCRM использует GitHub Actions с двумя workflow-файлами:
| Workflow | Файл | Триггер | Назначение |
|---|---|---|---|
| CI | .github/workflows/ci.yml |
push/PR в main/master |
Полная проверка: lint, тесты, сборка |
| PR Check | .github/workflows/pr-check.yml |
PR в main/master |
Быстрая проверка: lint + typecheck |
8.1. CI (ci.yml)
Основной пайплайн, запускается при каждом push или PR в ветки main/master.
+---------+
| lint | (10 мин)
| ESLint |
| Prettier|
+----+----+
|
+--------+--------+
| |
+-----+------+ +-----+------+
| test | | build |
| Jest | | turbo build|
| PostgreSQL | | |
| Redis | | |
+------------+ +------------+
(15 мин) (20 мин)
Job 1: Lint & Format (10 минут)
- Установка Node.js 22 + pnpm 9
pnpm install --frozen-lockfilepnpm lint(ESLint)pnpm format --check(Prettier)
Job 2: Backend Tests (15 минут, зависит от lint)
- Запускает service-контейнеры: PostgreSQL 17 + Redis 8
- Генерирует Prisma-клиент
- Применяет схему к тестовой БД (
prisma db push) - Запускает Jest-тесты backend
Переменные окружения для тестов:
DATABASE_URL=postgresql://fitcrm:test@localhost:5432/fitcrm_test?schema=public
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_SECRET=ci-test-jwt-secret-key-minimum-32-chars
JWT_REFRESH_SECRET=ci-test-jwt-refresh-secret-key-min-32
Job 3: Build All Apps (20 минут, зависит от lint)
- Генерирует Prisma-клиент
- Собирает все пакеты и приложения через
pnpm build(Turborepo)
Оптимизации:
concurrencyсcancel-in-progress: true-- отменяет предыдущие запуски для той же ветки- Кэширование pnpm через
actions/setup-nodeс опциейcache: 'pnpm' --frozen-lockfileгарантирует воспроизводимость сборки
8.2. PR Check (pr-check.yml)
Быстрая проверка для pull requests. Один job:
- ESLint
- Prettier
- TypeScript typecheck (
tsc --noEmit)
Запускается параллельно с CI, дает быструю обратную связь разработчику.
8.3. Dependabot
Файл .github/dependabot.yml настраивает автоматическое обновление зависимостей через GitHub Dependabot.
8.4. Расширение пайплайна (рекомендации)
Для production рекомендуется добавить следующие шаги:
# Пример: job для сборки и публикации Docker-образов
deploy:
name: Build & Push Docker Images
runs-on: ubuntu-latest
needs: [test, build]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
file: infra/docker/Dockerfile.api
push: true
tags: ghcr.io/${{ github.repository }}/api:${{ github.sha }}
9. SSL/TLS и безопасность
9.1. SSL-сертификаты
Let's Encrypt (рекомендуется для production):
# Установка certbot
sudo apt install certbot python3-certbot-nginx
# Получение сертификатов
sudo certbot --nginx -d api.fitcrm.example.com \
-d reception.fitcrm.example.com \
-d club.fitcrm.example.com \
-d admin.fitcrm.example.com
# Автообновление (cron)
sudo certbot renew --dry-run
STUN/TURN over TLS:
coturn слушает порт 5349 для TLS-соединений. Для production сконфигурируйте сертификаты в turnserver.conf:
cert=/etc/letsencrypt/live/turn.fitcrm.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.fitcrm.example.com/privkey.pem
9.2. Безопасность секретов
- Никогда не коммитьте
.envфайл (он добавлен в.gitignore) - Используйте разные секреты для каждого окружения (dev, staging, production)
- В CI/CD храните секреты в GitHub Secrets
- Для production рекомендуется использовать vault-решения (HashiCorp Vault, AWS Secrets Manager)
- JWT-секреты должны содержать минимум 32 символа
9.3. Защита PostgreSQL
-- Создание отдельных ролей
CREATE ROLE fitcrm_app WITH LOGIN PASSWORD '...';
CREATE ROLE fitcrm_admin WITH LOGIN PASSWORD '...' BYPASSRLS;
-- Ограничение прав приложения
GRANT CONNECT ON DATABASE fitcrm TO fitcrm_app;
GRANT USAGE ON SCHEMA public TO fitcrm_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO fitcrm_app;
-- Для миграций используйте fitcrm_admin
9.4. Защита Redis
- Всегда устанавливайте пароль (
requirepass) - В production закройте порт 6379 от внешнего доступа
- Используйте ACL для разграничения прав (Redis 6+)
9.5. Сетевая безопасность
Файрволл (ufw):
# Разрешить только необходимые порты
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (redirect to HTTPS)
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 3478/udp # STUN/TURN
sudo ufw allow 3478/tcp # STUN/TURN
sudo ufw allow 5349/udp # STUN/TURN TLS
sudo ufw allow 5349/tcp # STUN/TURN TLS
sudo ufw allow 49152:65535/udp # Media relay
sudo ufw enable
Важно: PostgreSQL (5432) и Redis (6379) НЕ должны быть доступны из интернета.
9.6. Суперадминистратор
Панель суперадминистратора (admin.fitcrm.example.com) требует дополнительных мер:
- Обязательная двухфакторная аутентификация (TOTP)
- Опциональный IP-whitelisting
- Полный audit log всех действий
9.7. Rate Limiting
API защищено глобальным rate limiter через @nestjs/throttler:
| Параметр | Значение |
|---|---|
| Лимит | 1000 запросов в минуту |
| Хранилище | Redis |
| Применение | Глобальный APP_GUARD (все эндпоинты) |
При превышении лимита возвращается 429 Too Many Requests.
Мониторинг rate limiting:
# Проверка ключей throttler в Redis
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" KEYS "throttler:*"
10. Резервное копирование и восстановление
10.1. PostgreSQL
Ежедневный бэкап (cron):
#!/bin/bash
# /opt/fitcrm/scripts/backup-db.sh
BACKUP_DIR="/opt/fitcrm/backups/postgres"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
mkdir -p "$BACKUP_DIR"
# Создание бэкапа
docker exec fitcrm-postgres pg_dump \
-U fitcrm \
-d fitcrm \
--format=custom \
--compress=9 \
> "$BACKUP_DIR/fitcrm_${TIMESTAMP}.dump"
# Проверка успешности
if [ $? -eq 0 ]; then
echo "[$(date)] Бэкап создан: fitcrm_${TIMESTAMP}.dump"
else
echo "[$(date)] ОШИБКА: бэкап не удался!" >&2
exit 1
fi
# Удаление старых бэкапов
find "$BACKUP_DIR" -name "*.dump" -mtime +${RETENTION_DAYS} -delete
echo "[$(date)] Очистка завершена. Бэкапы старше ${RETENTION_DAYS} дней удалены."
Настройка cron:
# Ежедневно в 03:00
0 3 * * * /opt/fitcrm/scripts/backup-db.sh >> /var/log/fitcrm-backup.log 2>&1
Восстановление из бэкапа:
# Остановить API
docker compose -f infra/docker/docker-compose.yml stop api 2>/dev/null || true
# Восстановление
docker exec -i fitcrm-postgres pg_restore \
-U fitcrm \
-d fitcrm \
--clean \
--if-exists \
< /opt/fitcrm/backups/postgres/fitcrm_20260218_030000.dump
# Запустить API
docker compose -f infra/docker/docker-compose.yml start api 2>/dev/null || true
10.2. Redis
Redis использует AOF-персистенцию (appendonly yes). Данные автоматически сохраняются в volume redis_data.
Ручной бэкап:
# Команда BGSAVE для создания RDB-снимка
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" BGSAVE
# Копирование файла
docker cp fitcrm-redis:/data/dump.rdb /opt/fitcrm/backups/redis/dump_$(date +%Y%m%d).rdb
10.3. S3-хранилище
Файлы (аватары, отчеты, логотипы) хранятся в S3-совместимом хранилище с префиксом /{club_id}/. Рекомендуется настроить:
- Versioning на бакете
- Cross-region replication (для критичных данных)
- Lifecycle policies для автоматического удаления старых файлов
10.4. Стратегия бэкапов
| Компонент | Частота | Хранение | Метод |
|---|---|---|---|
| PostgreSQL | Ежедневно 03:00 | 30 дней | pg_dump --format=custom |
| PostgreSQL (WAL) | Непрерывно | 7 дней | WAL-G / pgBackRest |
| Redis | Ежедневно | 7 дней | RDB snapshot |
| S3-файлы | Versioning | 90 дней | S3 versioning |
| .env / конфиги | При изменении | Бессрочно | Зашифрованное хранилище |
11. Масштабирование
11.1. Текущая архитектура (MVP)
В MVP все клубы работают на одном инстансе PostgreSQL с Row-Level Security:
[Клиенты] --> [Nginx/Traefik] --> [API (NestJS)]
|
[PostgreSQL + RLS]
|
[Redis]
11.2. Горизонтальное масштабирование API
API-сервер NestJS является stateless (состояние хранится в PostgreSQL и Redis). Можно запустить несколько инстансов:
# docker-compose.prod.yml (пример масштабирования)
api:
deploy:
replicas: 3
resources:
limits:
cpus: "2.0"
memory: 2G
При этом reverse proxy (Nginx/Traefik) выполняет балансировку нагрузки.
11.3. PostgreSQL: оптимизация
Рекомендуемые параметры для production (8 ГБ RAM):
# postgresql.conf
shared_buffers = 2GB
effective_cache_size = 6GB
maintenance_work_mem = 512MB
work_mem = 16MB
max_connections = 200
wal_buffers = 64MB
checkpoint_completion_target = 0.9
random_page_cost = 1.1 # для SSD
Пул соединений (PgBouncer):
При большом количестве клубов рекомендуется PgBouncer перед PostgreSQL:
docker run -d \
--name fitcrm-pgbouncer \
--network fitcrm-network \
-e DATABASE_URL="postgresql://fitcrm:password@postgres:5432/fitcrm" \
-e POOL_MODE=transaction \
-e MAX_DB_CONNECTIONS=100 \
-e DEFAULT_POOL_SIZE=25 \
-p 6432:6432 \
edoburu/pgbouncer
11.4. Redis: кластеризация
Для высоких нагрузок:
- Redis Sentinel (высокая доступность)
- Redis Cluster (шардирование при больших объемах данных BullMQ)
11.5. Эволюция multi-tenancy
FitCRM спроектирован для поэтапной миграции:
| Этап | Стратегия | Когда переходить |
|---|---|---|
| MVP | Shared DB + RLS | До 50 клубов |
| Стандартная | DB-per-tenant | 50-500 клубов, повышенные требования к изоляции |
| Премиум | K8s namespace | VIP-клубы, требующие полной изоляции |
Код использует Strategy Pattern (TenantStrategy), что позволяет переключать стратегии без переписывания бизнес-логики.
11.6. Kubernetes (production)
Для production-развертывания планируется использование Kubernetes. Манифесты будут размещены в infra/k8s/. Ключевые компоненты:
- Deployment: API (3+ реплики), web-приложения (2+ реплики)
- Service: ClusterIP для внутренних сервисов
- Ingress: Traefik/Nginx для маршрутизации по доменам
- PersistentVolumeClaim: для PostgreSQL и Redis
- HorizontalPodAutoscaler: автомасштабирование API по CPU/RAM
- Secrets: для хранения паролей и ключей
12. Устранение неполадок
12.1. Docker Compose не запускается
Проблема: Cannot connect to the Docker daemon
# Проверить статус Docker
sudo systemctl status docker
# Перезапустить Docker
sudo systemctl restart docker
# Проверить, что пользователь в группе docker
groups | grep docker
Проблема: port is already allocated
# Найти процесс, занимающий порт
sudo lsof -i :5432
sudo lsof -i :6379
# Или убить процесс
sudo kill -9 $(sudo lsof -t -i :5432)
12.2. PostgreSQL не запускается
Проблема: FATAL: password authentication failed
# Проверить переменные окружения
docker exec fitcrm-postgres env | grep POSTGRES
# Пересоздать контейнер с чистым volume (УДАЛИТ ДАННЫЕ!)
docker compose -f infra/docker/docker-compose.yml down -v
docker compose -f infra/docker/docker-compose.yml up -d
Проблема: too many connections
# Проверить количество соединений
docker exec fitcrm-postgres psql -U fitcrm -c "SELECT count(*) FROM pg_stat_activity;"
# Убить idle-соединения
docker exec fitcrm-postgres psql -U fitcrm -c \
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND query_start < now() - interval '10 minutes';"
12.3. Redis не доступен
Проблема: NOAUTH Authentication required
# Проверить, что REDIS_PASSWORD совпадает
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" ping
# Ожидаемый ответ: PONG
Проблема: OOM command not allowed when used memory > maxmemory
# Проверить использование памяти
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" INFO memory
# Установить maxmemory-policy
docker exec fitcrm-redis redis-cli -a "$REDIS_PASSWORD" CONFIG SET maxmemory-policy allkeys-lru
12.4. Prisma: ошибки миграций
Проблема: P1001: Can't reach database server
# Проверить, что PostgreSQL запущен и здоров
docker compose -f infra/docker/docker-compose.yml ps postgres
# Проверить DATABASE_URL
echo $DATABASE_URL
# Проверить подключение напрямую
docker exec fitcrm-postgres pg_isready -U fitcrm -d fitcrm
Проблема: P3009: migrate found failed migrations
# Посмотреть статус миграций
cd apps/api && npx prisma migrate status
# Отметить проблемную миграцию как примененную (осторожно!)
npx prisma migrate resolve --applied <migration_name>
# Или откатить вручную и повторить
npx prisma migrate resolve --rolled-back <migration_name>
npx prisma migrate deploy
12.5. Сборка приложений не проходит
Проблема: Cannot find module '@fitcrm/shared-types'
# Пересобрать пакеты в правильном порядке
pnpm --filter shared-types build
pnpm --filter validators build
pnpm --filter api-client build
pnpm build
Проблема: ENOMEM: not enough memory при сборке
# Увеличить лимит памяти для Node.js
export NODE_OPTIONS="--max-old-space-size=4096"
pnpm build
12.6. coturn (STUN/TURN)
Проблема: SIP-звонки не проходят
# Проверить логи coturn
docker logs fitcrm-coturn --tail 100
# Тестирование STUN (из браузера)
# Откройте: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
# STUN: stun:<SERVER_IP>:3478
# TURN: turn:<SERVER_IP>:3478 (с credentials)
Проблема: Allocation failed: cannot allocate relay
# Проверить, что диапазон UDP-портов открыт
sudo ufw status | grep 49152
# Проверить сетевые интерфейсы в конфигурации coturn
docker exec fitcrm-coturn cat /etc/turnserver.conf
12.7. CI/CD не проходит
Проблема: pnpm install --frozen-lockfile не проходит
# Обновить lockfile локально
pnpm install
git add pnpm-lock.yaml
git commit -m "chore: update pnpm-lock.yaml"
git push
Проблема: Тесты проходят локально, но не в CI
Возможные причины:
- Различия в переменных окружения (проверьте
envв ci.yml) - Тесты зависят от порядка выполнения (используйте
--runInBand) - Превышен timeout (по умолчанию 15 минут для тестов)
12.8. Общие рекомендации по диагностике
# Статус всех контейнеров
docker compose -f infra/docker/docker-compose.yml ps
# Логи всех сервисов (последние 50 строк)
docker compose -f infra/docker/docker-compose.yml logs --tail 50
# Использование ресурсов контейнерами
docker stats --no-stream
# Проверить сеть Docker
docker network inspect fitcrm-network
# Проверить volumes
docker volume ls | grep fitcrm
# Полная очистка (УДАЛИТ ВСЕ ДАННЫЕ!)
docker compose -f infra/docker/docker-compose.yml down -v --remove-orphans
docker system prune -f
Приложение: карта портов
| Порт | Протокол | Сервис | Доступ в production |
|---|---|---|---|
| 3000 | TCP | API (NestJS) | Только через reverse proxy |
| 3001 | TCP | web-admin (Next.js) | Только через reverse proxy |
| 3002 | TCP | web-club-admin (Next.js) | Только через reverse proxy |
| 3003 | TCP | web-platform-admin (Next.js) | Только через reverse proxy |
| 5432 | TCP | PostgreSQL | Закрыт извне |
| 6379 | TCP | Redis | Закрыт извне |
| 3478 | UDP/TCP | STUN/TURN | Открыт (необходим для VoIP) |
| 5349 | UDP/TCP | STUN/TURN over TLS | Открыт (необходим для VoIP) |
| 49152-65535 | UDP | Media relay (RTP/SRTP) | Открыт (необходим для VoIP) |
Приложение: структура файлов инфраструктуры
infra/
├── docker/
│ ├── docker-compose.yml # Development: PostgreSQL + Redis + coturn
│ ├── docker-compose.prod.yml # Production: + API + 3 web-приложения + лимиты ресурсов
│ ├── Dockerfile.api # NestJS API (3 стадии: deps -> builder -> runner)
│ ├── Dockerfile.web # web-admin Next.js (standalone output)
│ ├── Dockerfile.club-admin # web-club-admin Next.js (standalone output)
│ └── Dockerfile.platform-admin # web-platform-admin Next.js (standalone output)
├── stun-turn/
│ └── turnserver.conf # Конфигурация coturn STUN/TURN сервера
├── k8s/ # Kubernetes манифесты (планируется)
└── github-actions/ # Дополнительные workflow (планируется)
.github/
├── workflows/
│ ├── ci.yml # CI: lint -> test -> build
│ └── pr-check.yml # PR: lint + typecheck
└── dependabot.yml # Автообновление зависимостей