Files
akademiawodociagowca/docker-compose.yml
T
2026-09-02 08:21:27 +02:00

233 lines
8.7 KiB
YAML

services:
postgres:
image: postgres:16
container_name: webinar-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "${DB_PORT_EXPOSED:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 10
minio:
image: minio/minio:RELEASE.2024-12-18T13-15-44Z
container_name: webinar-minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
volumes:
- minio-data:/data
ports:
- "${MINIO_PORT_EXPOSED:-9000}:9000"
- "${MINIO_CONSOLE_PORT_EXPOSED:-9001}:9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 10
backend:
build:
context: ./backend
container_name: webinar-backend
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
minio:
condition: service_healthy
environment:
SERVER_PORT: 8080
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
JWT_EXPIRATION_MINUTES: ${JWT_EXPIRATION_MINUTES}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
SWAGGER_ENABLED: ${SWAGGER_ENABLED}
MINIO_ENDPOINT: http://minio:9000
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_BUCKET: ${MINIO_BUCKET}
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: ${MAIL_PORT}
MAIL_USER: ${MAIL_USER}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_FROM: ${MAIL_FROM}
MAIL_SMTP_AUTH: ${MAIL_SMTP_AUTH}
MAIL_SMTP_STARTTLS: ${MAIL_SMTP_STARTTLS}
VIDEO_PROVIDER: ${VIDEO_PROVIDER}
# Adres serwera LiveKit widziany przez przegladarke uczestnika.
# Konfiguracja kluczy idzie przez panel, tak samo jak SMTP.
LIVEKIT_URL: ${LIVEKIT_URL:-}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS}
LOG_LEVEL_ROOT: ${LOG_LEVEL_ROOT}
LOG_LEVEL_APP: ${LOG_LEVEL_APP}
ports:
- "${BACKEND_PORT_EXPOSED:-8080}:8080"
frontend:
build:
context: ./frontend
container_name: webinar-frontend
restart: unless-stopped
depends_on:
- backend
ports:
- "${FRONTEND_PORT_EXPOSED:-8081}:80"
# --- profil livekit: wlasny serwer wideo ---
#
# DLACZEGO PROFIL, A NIE ZWYKLE USLUGI
# -------------------------------------------------------------------------
# Instancja w trybie NONE albo na Jitsi nie potrzebuje serwera wideo, a te
# dwa kontenery zajmuja pamiec i otwieraja porty. Profil sprawia, ze
# `docker compose up` ich nie dotyka: uruchamiaja sie wylacznie na zadanie,
# poleceniem `docker compose --profile livekit up -d`.
livekit:
# Wersja serwera musi nadazac za `livekit-client` we froncie.
#
# Klient 2.22 laczy sie sciezka `/rtc/v1`, ktorej serwer 1.8 nie zna:
# przegladarka dostaje 404 i komunikat "v1 RTC path not found. Consider
# upgrading your LiveKit server version". Sala wyglada wtedy na dzialajaca
# (uczestnik wchodzi, widzi liste obecnych), ale nie plynie ani obraz,
# ani dzwiek. Podnoszac `livekit-client`, sprawdz wersje serwera.
image: livekit/livekit-server:v1.13
container_name: webinar-livekit
profiles: ["livekit"]
restart: unless-stopped
command: --config /etc/livekit.yaml
volumes:
- ./livekit/livekit.yaml:/etc/livekit.yaml:ro
environment:
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
# Zakres portow UDP. Ma pierwszenstwo przed wartoscia w livekit.yaml,
# bo serwer nie rozwija zmiennych w pliku konfiguracyjnym. Instancja,
# ktora musi ominac porty zajete w systemie (na Windows sa rezerwacje
# Hyper-V), zmienia wylacznie LIVEKIT_UDP_RANGE w .env.
UDP_PORT: ${LIVEKIT_UDP_RANGE:-50000-50100}
# Adres rozglaszany uczestnikom. Przy uruchomieniu lokalnym ustaw
# NODE_IP=127.0.0.1 w .env: bez tego serwer rozglasza publiczny adres
# lacza, a przegladarka na tej samej maszynie nie zestawi polaczenia.
NODE_IP: ${LIVEKIT_NODE_IP:-}
ports:
# sygnalizacja WebSocket i API serwera
- "${LIVEKIT_PORT:-7880}:7880"
# RTC po TCP: droga zapasowa, gdy UDP jest zablokowane w sieci klienta
- "${LIVEKIT_TCP_PORT:-7881}:7881"
# media po UDP. To jest **glowna** droga ruchu audio i wideo:
# bez tego zakresu uczestnicy widza sie, ale sie nie slysza
# Ten sam zakres po obu stronach: UDP_PORT ustawia go wewnatrz
# kontenera, wiec mapowanie musi go odwzorowac jeden do jednego.
# Rozjechanie stron konczy sie bledem "invalid ranges specified".
- "${LIVEKIT_UDP_RANGE:-50000-50100}:${LIVEKIT_UDP_RANGE:-50000-50100}/udp"
# TURN: przekazuje ruch, gdy polaczenie bezposrednie jest niemozliwe.
#
# Potrzebny uczestnikom za restrykcyjnym NAT-em albo firewallem firmowym,
# czyli w praktyce **wiekszosci uczestnikow szkolen korporacyjnych**.
# Bez TURN czesc osob dolaczy do pokoju i nie zobaczy ani nie uslyszy nikogo.
# Redis: kanal komunikacji miedzy serwerem LiveKit a Egress.
#
# Egress nie rozmawia z serwerem bezposrednio, tylko przez Redisa: serwer
# wrzuca tam zadanie nagrania, Egress je odbiera. **Bez Redisa serwer
# przyjmuje zadanie i nie ma go komu przekazac**, a nagrywanie konczy sie
# timeoutem przy poprawnie dzialajacym serwerze.
#
# W tym samym profilu co reszta wideo: instancja bez nagrywania nie ma
# powodu trzymac Redisa.
redis:
image: redis:7-alpine
container_name: webinar-redis
profiles: ["livekit"]
restart: unless-stopped
command: redis-server --save "" --appendonly no
ports:
# Wystawiony **wylacznie na petli zwrotnej** i tylko po to, zeby Egress
# w sieci hosta (uruchomienie lokalne) mial jak sie polaczyc. Na produkcji,
# gdzie Egress zostaje w sieci Dockera, te linie mozna usunac.
- "127.0.0.1:${REDIS_PORT_EXPOSED:-6379}:6379"
# Bez wolumenu i bez zapisu na dysk: to kolejka zadan na chwile, a nie
# magazyn. Utrata jej zawartosci przy restarcie nic nie kosztuje.
# Egress: nagrywa pokoj i zapisuje plik prosto do MinIO.
#
# DLACZEGO OSOBNY KONTENER
# -------------------------------------------------------------------------
# Egress uruchamia **prawdziwa przegladarke** (Chrome), wchodzi nia do pokoju
# i nagrywa to, co widzi. Stad wymagania nietypowe dla zwyklej uslugi:
# uprawnienie SYS_ADMIN dla piaskownicy Chrome i powiekszony /dev/shm, bo
# domyslne 64 MB nie wystarcza przegladarce i konczy sie jej wysypaniem
# w polowie nagrania.
livekit-egress:
# Przypiete do tej samej wersji co w produkcji. `latest` pobieralo
# nieprzewidywalny obraz, a niezgodnosc z serwerem LiveKit ujawnia sie
# dopiero przy probie nagrania.
image: livekit/egress:v1.9
container_name: webinar-livekit-egress
profiles: ["livekit"]
restart: unless-stopped
depends_on:
- livekit
- redis
environment:
# Konfiguracja idzie zmienna, a nie plikiem: Egress czyta ja w calosci
# z EGRESS_CONFIG_BODY, wiec nie ma czego montowac.
#
# `ws_url` i `redis` musza wskazywac adresy **w sieci Dockera**, a nie
# te widziane z przegladarki: to ruch miedzy uslugami.
EGRESS_CONFIG_BODY: |
redis:
address: ${EGRESS_REDIS_ADDR:-redis:6379}
api_key: ${LIVEKIT_API_KEY}
api_secret: ${LIVEKIT_API_SECRET}
ws_url: ${EGRESS_WS_URL:-ws://livekit:7880}
insecure: true
log_level: info
template_base: ${EGRESS_TEMPLATE_BASE:-http://frontend/}
cap_add:
# Piaskownica Chrome. Bez tego przegladarka nie wstaje i nagranie
# konczy sie bledem jeszcze przed wejsciem do pokoju.
- SYS_ADMIN
# Pamiec wspoldzielona przegladarki. Domyslne 64 MB wystarcza na kilka
# sekund nagrania, potem Chrome sie wysypuje.
shm_size: 1gb
coturn:
image: coturn/coturn:4.6
container_name: webinar-coturn
profiles: ["livekit"]
restart: unless-stopped
network_mode: host
command: >
-n
--realm=${TURN_REALM:-webinar.local}
--user=${TURN_USER:-webinar}:${TURN_PASSWORD}
--lt-cred-mech
--fingerprint
--no-cli
--no-tlsv1
--no-tlsv1_1
--min-port=${TURN_MIN_PORT:-49160}
--max-port=${TURN_MAX_PORT:-49200}
# network_mode: host, bo TURN musi widziec prawdziwe adresy IP:
# za NAT-em Dockera rozdawalby uczestnikom adresy wewnetrzne kontenera,
# pod ktore nikt z zewnatrz sie nie polaczy
volumes:
postgres-data:
minio-data: