121 lines
6.9 KiB
Nginx Configuration File
121 lines
6.9 KiB
Nginx Configuration File
# Dev reverse proxy: backend API + раздача фронтенд-SPA (статика в образе).
|
||
# Завершение TLS для production документировано в docs/deploy/dev-setup.md
|
||
# и должно добавляться отдельным server-блоком (443) с реальными сертификатами.
|
||
#
|
||
# ВАЖНО (stale DNS): статический `upstream { server backend:8000; }` nginx
|
||
# резолвит один раз при старте/reload и держит IP в памяти. После пересоздания
|
||
# контейнера backend (`docker compose up -d --force-recreate backend`) Docker
|
||
# выдаёт ему новый IP, а nginx продолжает стучаться по старому → 502, пока
|
||
# nginx не перезапустят. Чтобы резолвить имя заново на каждый запрос,
|
||
# используем embedded DNS Docker (127.0.0.11) через directive `resolver` и
|
||
# ПЕРЕМЕННУЮ в proxy_pass — переменные nginx не кэшируются на старте и
|
||
# резолвятся заново по истечении `valid=`.
|
||
resolver 127.0.0.11 valid=10s ipv6=off;
|
||
|
||
map $http_upgrade $connection_upgrade {
|
||
default upgrade;
|
||
'' close;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name _;
|
||
|
||
# --- Backend API ---
|
||
# Проброс через переменную: proxy_pass с переменной НЕ выполняет
|
||
# автоматическую подстановку URI (в отличие от статического
|
||
# `proxy_pass http://upstream/prefix/;`), поэтому URI-часть в proxy_pass
|
||
# не указываем — nginx передаёт исходный URI запроса как есть (path +
|
||
# query), что и сохраняет прежний маппинг /api/ -> backend:8000/api/
|
||
# (префикс совпадает 1-в-1, подмены не требовалось и раньше).
|
||
# Upgrade-заголовки нужны и обычным HTTP-запросам (без `Upgrade` в запросе
|
||
# `map $http_upgrade $connection_upgrade` выше подставляет `close`, что не
|
||
# ломает keep-alive обычных ответов), и WS-эндпоинту чата конференции
|
||
# (`WS /api/v1/conferences/{id}/chat`) — он живёт под тем же
|
||
# префиксом /api/, а не под отдельным /ws/.
|
||
location /api/ {
|
||
set $backend_upstream http://backend:8000;
|
||
proxy_pass $backend_upstream;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
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;
|
||
proxy_read_timeout 3600s;
|
||
}
|
||
|
||
# --- WebSocket upgrade для чата и LiveKit signaling через nginx ---
|
||
location /ws/ {
|
||
set $backend_upstream http://backend:8000;
|
||
proxy_pass $backend_upstream;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
proxy_set_header Host $host;
|
||
proxy_read_timeout 3600s;
|
||
}
|
||
|
||
# --- LiveKit SFU signaling (WebSocket) ---
|
||
# Доступен только когда стек запущен с профилем `media`
|
||
# (см. docker-compose.yml) — без него resolver вернёт NXDOMAIN на первый
|
||
# запрос к этому location, что ожидаемо.
|
||
# Здесь префикс /livekit/ нужно СНЯТЬ (как раньше делал
|
||
# `proxy_pass http://livekit_upstream/;`). С переменной в proxy_pass
|
||
# автоматическая подмена недоступна, поэтому переписываем URI явно через
|
||
# rewrite ... break — nginx передаст на upstream уже переписанный $uri.
|
||
# ВАЖНО: `set` должен идти ДО `rewrite ... break` — break прерывает
|
||
# выполнение всех последующих директив модуля rewrite (включая set) в
|
||
# этом location, иначе переменная останется неинициализированной.
|
||
location /livekit/ {
|
||
set $livekit_upstream http://livekit:7880;
|
||
rewrite ^/livekit/(.*)$ /$1 break;
|
||
proxy_pass $livekit_upstream;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $connection_upgrade;
|
||
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_read_timeout 3600s;
|
||
}
|
||
|
||
# --- Метрики backend: `GET /metrics` без авторизации
|
||
# внутри приложения (backend/api/metrics.py) — снаружи периметра нарочно
|
||
# закрываем явным запретом. Это ОБЯЗАТЕЛЬНО именно exact-match
|
||
# (`location = /metrics`): ниже есть catch-all `location /` (раздача
|
||
# фронтенд-SPA), который иначе отдал бы на /metrics файл фронта (или
|
||
# index.html через try_files). Exact-match в nginx приоритетнее любого
|
||
# prefix-location, поэтому 403 срабатывает раньше SPA-раздачи.
|
||
# Prometheus (профиль compose `monitoring`) ходит в backend НАПРЯМУЮ по
|
||
# внутренней docker-сети (`backend:8000/metrics`), минуя nginx.
|
||
location = /metrics {
|
||
return 403;
|
||
}
|
||
|
||
# --- Медиа (аватары): раздача напрямую из volume, в обход backend. ---
|
||
location /media/ {
|
||
alias /media/;
|
||
autoindex off;
|
||
}
|
||
|
||
# --- Frontend SPA (React + Vite): статика вкомпилирована в образ nginx
|
||
# (frontend/Dockerfile) в /usr/share/nginx/html. `try_files` с
|
||
# history-fallback на /index.html нужен для клиентского роутинга
|
||
# react-router (deep-ссылки вида /conferences/<id>, /admin/settings —
|
||
# при перезагрузке страницы отдаётся index.html, дальше роутит SPA).
|
||
# Более специфичные location выше (/api/, /media/, = /metrics, /livekit/,
|
||
# /ws/) матчатся раньше этого catch-all, поэтому API и метрики не
|
||
# перехватываются фронтом.
|
||
location / {
|
||
root /usr/share/nginx/html;
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
|
||
location /healthz {
|
||
return 200 "ok\n";
|
||
add_header Content-Type text/plain;
|
||
}
|
||
}
|