version: "3.8" # ============================================================================ # EasyStream - Production Docker Compose Configuration # ============================================================================ # Usage: docker-compose -f docker-compose.prod.yml up -d # # IMPORTANT: Before deployment: # 1. Copy .env.production to .env and fill in all values # 2. Generate secure secrets for all services # 3. Set up SSL/TLS certificates # 4. Configure external volumes for data persistence # 5. Set up monitoring and logging # ============================================================================ services: db: image: mariadb:10.6 container_name: easystream-db-prod restart: always environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password MYSQL_DATABASE: ${DB_NAME:-easystream} MYSQL_USER: ${DB_USER:-easystream} MYSQL_PASSWORD_FILE: /run/secrets/db_password ports: - "127.0.0.1:3306:3306" # Only bind to localhost volumes: - db_data:/var/lib/mysql - ./__install/easystream.sql:/docker-entrypoint-initdb.d/1-main_schema.sql:ro - ./__install/add_advanced_features.sql:/docker-entrypoint-initdb.d/2-advanced_features.sql:ro - ./deploy/init_settings.sql:/docker-entrypoint-initdb.d/3-init_settings.sql:ro - ./deploy/backup:/backup # For database backups secrets: - db_root_password - db_password healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u ${DB_USER:-easystream} --silent || exit 1"] start_period: 120s interval: 30s timeout: 10s retries: 5 networks: - backend logging: driver: "json-file" options: max-size: "10m" max-file: "3" php: build: context: . dockerfile: Dockerfile.php args: PHP_VERSION: 8.2 image: easystream-php:production container_name: easystream-php-prod restart: always working_dir: /srv/easystream environment: TZ: ${TZ:-UTC} DB_HOST: db DB_NAME: ${DB_NAME:-easystream} DB_USER: ${DB_USER:-easystream} DB_PASS_FILE: /run/secrets/db_password REDIS_HOST: redis REDIS_PORT: 6379 REDIS_DB: 0 MAIN_URL: ${MAIN_URL} DEBUG: "false" APP_ENV: production PHP_MEMORY_LIMIT: 512M PHP_UPLOAD_MAX_FILESIZE: 256M PHP_POST_MAX_SIZE: 256M volumes: - ./:/srv/easystream:ro # Read-only for security - app_uploads:/srv/easystream/f_data/uploads - app_cache:/srv/easystream/f_data/cache - app_logs:/srv/easystream/f_data/logs - rtmp_hls:/var/www/hls:ro - rtmp_rec:/mnt/rec:ro secrets: - db_password - api_key - jwt_secret - encryption_key depends_on: db: condition: service_healthy redis: condition: service_healthy networks: - frontend - backend logging: driver: "json-file" options: max-size: "10m" max-file: "5" caddy: image: caddy:2-alpine container_name: easystream-caddy-prod restart: always depends_on: php: condition: service_started srs: condition: service_started ports: - "80:80" - "443:443" - "443:443/udp" # HTTP/3 volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - ./:/srv/easystream:ro - rtmp_hls:/var/www/hls:ro - caddy_data:/data - caddy_config:/config - ./deploy/ssl:/ssl:ro # For custom SSL certificates networks: - frontend logging: driver: "json-file" options: max-size: "10m" max-file: "3" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"] interval: 30s timeout: 10s retries: 3 srs: image: ossrs/srs:5 container_name: easystream-srs-prod restart: always ports: - "1935:1935" # RTMP ingest - "1985:1985" # HTTP API - "8080:8080" # HTTP Server volumes: - ./deploy/srs.conf:/usr/local/srs/conf/srs.conf:ro - rtmp_hls:/srs/hls - rtmp_rec:/srs/rec - srs_logs:/usr/local/srs/objs/logs command: ["/usr/local/srs/objs/srs", "-c", "/usr/local/srs/conf/srs.conf"] networks: - frontend - backend logging: driver: "json-file" options: max-size: "10m" max-file: "3" redis: image: redis:7-alpine container_name: easystream-redis-prod restart: always ports: - "127.0.0.1:6379:6379" # Only bind to localhost volumes: - redis_data:/data command: > redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru --requirepass ${REDIS_PASSWORD:-} --save 900 1 --save 300 10 --save 60 10000 networks: - backend healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 10s retries: 5 logging: driver: "json-file" options: max-size: "5m" max-file: "3" cron: build: context: . dockerfile: Dockerfile.cron image: easystream-cron:production container_name: easystream-cron-prod restart: always depends_on: php: condition: service_started environment: TZ: ${TZ:-UTC} DB_HOST: db DB_NAME: ${DB_NAME:-easystream} DB_USER: ${DB_USER:-easystream} DB_PASS_FILE: /run/secrets/db_password CRON_BASE_URL: ${MAIN_URL} CRON_SSK_FILE: /run/secrets/cron_secret VOD_REC_PATH: /mnt/rec REDIS_HOST: redis REDIS_PORT: 6379 REDIS_DB: 0 volumes: - ./:/srv/easystream:ro - rtmp_rec:/mnt/rec:ro - cron_logs:/var/log/cron secrets: - db_password - cron_secret networks: - backend logging: driver: "json-file" options: max-size: "5m" max-file: "3" queue-worker: build: context: . dockerfile: Dockerfile.php image: easystream-php:production container_name: easystream-worker-prod restart: always depends_on: db: condition: service_healthy redis: condition: service_healthy environment: TZ: ${TZ:-UTC} DB_HOST: db DB_NAME: ${DB_NAME:-easystream} DB_USER: ${DB_USER:-easystream} DB_PASS_FILE: /run/secrets/db_password REDIS_HOST: redis REDIS_PORT: 6379 WORKER_QUEUES: ${WORKER_QUEUES:-default,video,email,notifications} WORKER_SLEEP: ${WORKER_SLEEP:-3} WORKER_TIMEOUT: ${WORKER_TIMEOUT:-300} volumes: - ./:/srv/easystream:ro - app_uploads:/srv/easystream/f_data/uploads - rtmp_hls:/var/www/hls - rtmp_rec:/mnt/rec secrets: - db_password command: php f_scripts/queue_worker.php networks: - backend logging: driver: "json-file" options: max-size: "10m" max-file: "5" deploy: replicas: 2 # Run 2 workers for high availability abr: image: jrottenberg/ffmpeg:5.1-ubuntu container_name: easystream-abr-prod restart: always entrypoint: ["/bin/bash"] command: ["/abr.sh"] depends_on: srs: condition: service_started environment: ABR_STREAM_KEY: ${ABR_STREAM_KEY:-} volumes: - rtmp_hls:/var/www/hls - ./deploy/abr.sh:/abr.sh:ro networks: - backend logging: driver: "json-file" options: max-size: "5m" max-file: "3" # ============================================================================ # Docker Secrets (Production Security) # ============================================================================ # Create these files before deployment: # echo "your_secret" | docker secret create db_root_password - # echo "your_secret" | docker secret create db_password - # etc. # ============================================================================ secrets: db_root_password: file: ./secrets/db_root_password.txt db_password: file: ./secrets/db_password.txt api_key: file: ./secrets/api_key.txt jwt_secret: file: ./secrets/jwt_secret.txt encryption_key: file: ./secrets/encryption_key.txt cron_secret: file: ./secrets/cron_secret.txt # ============================================================================ # Persistent Volumes # ============================================================================ volumes: db_data: driver: local driver_opts: type: none o: bind device: /var/lib/easystream/db redis_data: driver: local app_uploads: driver: local driver_opts: type: none o: bind device: /var/lib/easystream/uploads app_cache: driver: local app_logs: driver: local driver_opts: type: none o: bind device: /var/log/easystream rtmp_hls: driver: local rtmp_rec: driver: local driver_opts: type: none o: bind device: /var/lib/easystream/recordings srs_logs: driver: local cron_logs: driver: local caddy_data: driver: local caddy_config: driver: local # ============================================================================ # Networks # ============================================================================ networks: frontend: driver: bridge backend: driver: bridge internal: true # Backend network not accessible from outside