Деплой и proxy¶
Production-деплой FARA через Docker Compose: основной стек
(backend, frontend, postgres, cron) + отдельный
nginx-proxy стек для HTTPS, Let's Encrypt и
WebSocket. Прокси живёт в собственном compose-файле и
подключается к сети основного стека через external network — это
даёт независимый рестарт прокси без даунтайма приложения.
Быстрый старт проще всего¶
Если нужен классический деплой без вникания в
детали — заполни .env и запусти
deploy.sh. Скрипт идемпотентен (можно перезапускать
сколько угодно — лишнего не сделает) и сам разберётся, что
нужно.
# 1. Клонируем
git clone https://github.com/shurshilov/faracrm.git /opt/faracrm
cd /opt/faracrm
# 2. Настраиваем окружение основного стека
cp .env.sample .env
nano .env # DOMAIN, POSTGRES_PASSWORD, SECRET_KEY
# 3. Поднимаем основной стек (backend, frontend, postgres, cron)
docker compose up -d
# 4. Настраиваем окружение proxy-стека
cd deploy/proxy
cp .env.example .env
nano .env # DOMAIN, EMAIL, FARA_NETWORK
# 5. Деплой proxy: SSL-сертификат + nginx-конфиг
chmod +x deploy.sh
./deploy.sh
После этого FARA доступна на твоём домене по HTTPS.
Что делает deploy.sh:
-
Проверяет, что установлен Docker и заданы все нужные
переменные в
.env. -
Проверяет, что docker-сеть основного стека
(
FARA_NETWORK) существует. - Поднимает nginx в bootstrap-режиме (только HTTP) — чтобы certbot мог пройти ACME-challenge.
- Запрашивает SSL-сертификат у Let's Encrypt (если ещё не выпущен).
-
Рендерит финальный nginx-конфиг из шаблона
fara.conf.templateчерезenvsubst. - Reload nginx и поднимает certbot для авто-renewal.
При повторном запуске сертификат не перезапрашивается —
обновляется только nginx-конфиг. Это безопасно после правок
шаблона или .env.
Если хочешь разобраться вручную
Дальше идёт детальное описание: как устроены compose-файлы, что и почему делает nginx, как пересоздавать БД, как обновлять код. Это нужно если хочешь нестандартный сетап (отдельный CDN, кластер, кастомные сертификаты).
Структура¶
/opt/faracrm/
├── docker-compose.yml ← основной стек
├── .env
├── deploy/
│ └── proxy/
│ ├── docker-compose.yml ← прокси-стек
│ ├── deploy.sh ← главный скрипт деплоя
│ ├── nginx/
│ │ ├── nginx.conf
│ │ └── templates/
│ │ └── fara.conf.template
│ └── certbot/ ← хранилище сертификатов
└── ...
Два compose-файла соединены
внешней docker-сетью
faracrm_default:
services:
fara-proxy:
networks:
- faracrm_default
networks:
faracrm_default:
external: true
Когда основной стек поднимается — сеть создаётся автоматически.
Прокси к ней просто подключается. При
docker compose down основного стека сеть остаётся,
потому что прокси держит её занятой.
Архитектура запросов¶
graph LR
User[Browser]
Proxy[fara-proxy<br/>nginx + certbot<br/>:80, :443]
BE[backend:8000]
FE[frontend:80]
DB[(postgres)]
User -->|HTTPS| Proxy
Proxy -->|/api/*<br/>strip prefix| BE
Proxy -->|/ws/*| BE
Proxy -->|/| FE
BE --> DB
style Proxy fill:#dde7ff,stroke:#5170c4
| Префикс | Куда | Зачем strip prefix |
|---|---|---|
/api/* |
backend:8000 (без /api) |
FastAPI-роутеры объявлены без /api, оставлять
префикс — ломать роутинг
|
/ws/* |
backend:8000 (с /ws) |
Backend сам ожидает /ws/chat |
/ |
frontend:80 |
SPA, отдаёт index.html |
deploy.sh — детально¶
При первом запуске нужно решить «куриный-яичный» парадокс: nginx
требует SSL-сертификат для запуска, а certbot требует
работающего nginx для прохождения ACME challenge.
deploy.sh обходит это так:
-
Создаёт временный bootstrap-конфиг для nginx
— только HTTP-listener на 80 порту, плюс path
/.well-known/acme-challenge/для certbot. Никакого SSL. - Поднимает nginx с этим конфигом — он уже отвечает на запросы по HTTP.
-
Запускает certbot через webroot — он кладёт challenge-файл в
/var/www/certbot/.well-known/..., Let's Encrypt стучится по HTTP, файл найден, сертификат выпущен. -
Рендерит финальный nginx-конфиг из шаблона
fara.conf.templateчерезenvsubst(подставляются переменные из.env). -
nginx -s reload— nginx подхватывает новый конфиг с SSL. -
docker compose up -d— поднимает certbot-контейнер, который в фоне обновляет сертификат каждые 12 часов.
Идемпотентность¶
При повторном запуске deploy.sh:
-
Сертификат не перезапрашивается — скрипт
видит существующий
./certbot/conf/live/$DOMAIN/fullchain.pemи пропускает шаг. -
Конфиг nginx перерендеривается заново — это
безопасно после правок шаблона или
.env. -
nginx -s reloadприменяет новый конфиг без даунтайма.
Это значит, что после изменения
fara.conf.template (например, добавил новый
location, поменял CSP) — достаточно ./deploy.sh, и
через 1-2 секунды правки уже в проде.
Принудительный перевыпуск сертификата¶
Если нужно перевыпустить сертификат (сменился домен, истёк, что-то сломалось):
Rate limit Let's Encrypt
Let's Encrypt разрешает 50 сертификатов на домен в неделю.
Если экспериментируешь с настройкой — выставляй
STAGING=1 в .env. Тогда сертификат
выпустится из staging-CA, лимит у которого больше, но в
браузере он будет невалидным.
Кириллический домен¶
Скрипт сам проверит DOMAIN на не-ASCII символы и
подскажет, как сконвертировать в punycode. Например, для домена
моя-срм.рф:
В .env нужно положить уже punycode-версию.
Punycode для кириллических доменов¶
Если используешь кириллический домен (например,
моя-срм.рф) — в nginx нельзя писать кириллицу
напрямую, нужно сконвертировать в punycode:
В .env:
В nginx-конфиге используется ${DOMAIN} — envsubst
подставляет значение при старте контейнера.
nginx-конфигурация — нюансы¶
Resolver обязателен¶
Если в proxy_pass использовать
переменную (а это нужно для разных upstream):
Без resolver директивы это даёт ошибку
no resolver defined. Решение — указать Docker DNS:
Это встроенный DNS Docker для имён сервисов внутри сети.
WebSocket location¶
WebSocket нельзя пускать через тот же location, что HTTP — нужны специфические заголовки:
location ~ ^/ws(/|$) {
set $backend_ws_upstream "backend:8000";
proxy_pass http://$backend_ws_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
Длинный таймаут необходим — WebSocket долгоживущий, общий 300s сразу будет рвать idle-соединения.
CSP¶
add_header Content-Security-Policy "default-src 'self'; connect-src 'self' wss://$host; ..." always;
wss://$host нужен — без этого браузер не пустит
WebSocket-соединение.
Пересоздание с нуля¶
Если нужно полностью обнулить БД (например, при отладке):
cd /opt/faracrm
# 1. Опустить только сервисы FARA, прокси не трогаем
docker compose down
# 2. Снести volumes — БД и filestore исчезнут
docker volume rm faracrm_pgdata faracrm_filestore_docker
# 3. Пересобрать (если меняли код)
docker compose build --no-cache
# 4. Поднять
docker compose up -d
docker compose logs backend --tail 80 -f
Прокси сам подцепится — после
docker compose up -d свежие контейнеры
backend/frontend подключатся к сети
faracrm_default, прокси их увидит по DNS.
Проверка:
Обновление кода¶
cd /opt/faracrm
# Вариант через гит (рекомендуемый)
git pull
docker compose build
docker compose down
docker compose up
cd /opt/faracrm
# Вариант через zip файл
unzip -o ~/some_patch.zip
cp -r some_patch/. ./
rm -rf some_patch
docker compose build
docker compose down
docker compose up
Frontend Vite билдится в production-режиме внутри Docker-образа
на этапе сборки — npm run build уже зашит в
Dockerfile. После rebuilt'а статика подменяется автоматически.
HSTS¶
В nginx-конфиге HSTS закомментирован для безопасной отладки в первые недели:
Раскомментировать через 1-2 недели стабильной работы HTTPS, когда уверены, что не нужно откатываться на HTTP.
Что мониторить¶
| Что | Где смотреть |
|---|---|
| Логи backend | docker compose logs backend -f |
| Логи nginx | docker logs fara-proxy -f |
| Срок сертификата |
docker exec fara-proxy ls -la
/etc/letsencrypt/live/<domain>/
|
| Активные WS |
docker exec backend python -c "from backend... import
ChatConnectionManager; ..."
|
| PG соединения |
docker exec postgres psql -U openpg -c "SELECT count(*)
FROM pg_stat_activity"
|
Certbot обновляет сертификаты автоматически (cron внутри
certbot-контейнера, --quiet). Если что-то ломается
— запустить вручную: