services: # MediaMTX - RTSP/HLS/WebRTC streaming server mediamtx: image: bluenviron/mediamtx:latest container_name: ${PROJECT_NAME}-mediamtx restart: unless-stopped network_mode: host volumes: - ./mediamtx.yml:/mediamtx.yml - ${RECORDINGS_FOLDER}:/recordings environment: MTX_PROTOCOLS: tcp # Main stream configuration MTX_PATHS_COURT_MAIN_SOURCE: ${MTX_PATHS_COURT_MAIN_SOURCE} MTX_PATHS_COURT_MAIN_SOURCEPROTOCOL: tcp MTX_PATHS_COURT_MAIN_RECORD: "yes" MTX_PATHS_COURT_MAIN_RECORDPATH: "/recordings/%path/%Y-%m-%d_%H-%M-%S-%f.mp4" MTX_PATHS_COURT_MAIN_RECORDFORMAT: fmp4 MTX_PATHS_COURT_MAIN_RECORDSEGMENTDURATION: 1h MTX_PATHS_COURT_MAIN_RECORDDELETEAFTER: 24h # Sub stream configuration (usually H264) MTX_PATHS_COURT_SUB_SOURCE: ${MTX_PATHS_COURT_SUB_SOURCE} MTX_PATHS_COURT_SUB_SOURCEPROTOCOL: tcp # Legacy court path - original H265 stream MTX_PATHS_COURT_SOURCE: ${MTX_PATHS_COURT_SOURCE} MTX_PATHS_COURT_SOURCEPROTOCOL: tcp # Force all paths to start immediately MTX_PATHDEFAULTS_SOURCEONDEMAND: "no" # Disable all authentication MTX_PATHDEFAULTS_PUBLISHUSER: "" MTX_PATHDEFAULTS_PUBLISHPASS: "" MTX_PATHDEFAULTS_READUSER: "" MTX_PATHDEFAULTS_READPASS: "" healthcheck: test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9997/v2/paths/list"] interval: 30s timeout: 10s retries: 3 # FFmpeg transcoder for H265 to H264 transcoder: image: linuxserver/ffmpeg:latest container_name: ${PROJECT_NAME}-transcoder restart: unless-stopped network_mode: host command: > -re -rtsp_transport tcp -i rtsp://localhost:8554/court -c:v libx264 -preset ultrafast -tune zerolatency -b:v 2M -f rtsp -rtsp_transport tcp rtsp://localhost:8554/court_h264 depends_on: - mediamtx # Score overlay and recording service overlay-service: build: ./overlay image: ${PROJECT_NAME}-overlay:latest container_name: ${PROJECT_NAME}-overlay restart: unless-stopped depends_on: - mediamtx volumes: - ${RECORDINGS_FOLDER}:/recordings environment: - SQUASHKIWI_API=${SQUASHKIWI_API} - COURT_ID=${COURT_ID} - MEDIAMTX_API=http://localhost:9997 - RECORDING_PATH=/recordings - IDLE_TIMEOUT=${IDLE_TIMEOUT} - RECORDING_RETENTION_DAYS=${RECORDING_RETENTION_DAYS} healthcheck: test: ["CMD", "pgrep", "-f", "overlay_service.py"] interval: 30s timeout: 10s retries: 3 # Nginx web server nginx: image: nginx:alpine container_name: ${PROJECT_NAME}-nginx restart: unless-stopped network_mode: host volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./web:/usr/share/nginx/html:ro - ${RECORDINGS_FOLDER}:/recordings:ro - nginx-cache:/var/cache/nginx depends_on: - mediamtx healthcheck: test: ["CMD", "wget", "-q", "-O", "-", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 # Optional: Cloudflare tunnel cloudflared: image: cloudflare/cloudflared:latest container_name: ${PROJECT_NAME}-tunnel restart: unless-stopped command: tunnel run environment: - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} profiles: - tunnel # Optional: Prometheus monitoring prometheus: image: prom/prometheus:latest container_name: ${PROJECT_NAME}-prometheus restart: unless-stopped ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus-data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' profiles: - monitoring # Optional: Grafana grafana: image: grafana/grafana:latest container_name: ${PROJECT_NAME}-grafana restart: unless-stopped ports: - "3000:3000" volumes: - grafana-data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} depends_on: - prometheus profiles: - monitoring volumes: nginx-cache: prometheus-data: grafana-data: networks: default: name: ${PROJECT_NAME} driver: bridge