version: "3.9" services: s1ne: build: context: . dockerfile: Dockerfile container_name: s1ne-s-1 restart: unless-stopped env_file: - .env environment: PYTHONUNBUFFERED: "1" PYTHONDONTWRITEBYTECODE: "1" APP_ENV: ${APP_ENV:-production} LOG_LEVEL: ${LOG_LEVEL:-INFO} SQLITE_CACHE_PATH: ${SQLITE_CACHE_PATH:-/app/data/cache.sqlite3} AUDIO_CACHE_TTL: ${AUDIO_CACHE_TTL:-600} SEARCH_CACHE_TTL: ${SEARCH_CACHE_TTL:-600} SOURCE_METADATA_CACHE_TTL: ${SOURCE_METADATA_CACHE_TTL:-1800} RECOMMENDATION_CACHE_TTL: ${RECOMMENDATION_CACHE_TTL:-900} NEGATIVE_CACHE_TTL: ${NEGATIVE_CACHE_TTL:-90} AUDIO_NEGATIVE_CACHE_TTL: ${AUDIO_NEGATIVE_CACHE_TTL:-90} CACHE_CLEANUP_INTERVAL_SECONDS: ${CACHE_CLEANUP_INTERVAL_SECONDS:-300} CACHE_CLEANUP_BATCH_SIZE: ${CACHE_CLEANUP_BATCH_SIZE:-1000} YTDLP_CONCURRENCY: ${YTDLP_CONCURRENCY:-5} SEARCH_YTDLP_CONCURRENCY: ${SEARCH_YTDLP_CONCURRENCY:-3} AUDIO_YTDLP_CONCURRENCY: ${AUDIO_YTDLP_CONCURRENCY:-2} RECOMMENDATION_YTDLP_CONCURRENCY: ${RECOMMENDATION_YTDLP_CONCURRENCY:-3} YTDLP_PLAYER_CLIENTS: ${YTDLP_PLAYER_CLIENTS:-web,android,mweb,ios} TRUSTED_PROXY_CIDRS: ${TRUSTED_PROXY_CIDRS} PROXY_FILE: ${PROXY_FILE:-/app/proxies.txt} PROXY_USER: ${PROXY_USER:-} PROXY_PASS: ${PROXY_PASS:-} PROXY_RETRY_ATTEMPTS: ${PROXY_RETRY_ATTEMPTS:-3} MEDIA_TOKEN_SECRET: ${MEDIA_TOKEN_SECRET} MEDIA_TOKEN_BIND_ANONYMOUS_ID: ${MEDIA_TOKEN_BIND_ANONYMOUS_ID:-true} volumes: - yt_music_data:/app/data - ${PROXY_FILE_HOST:-./proxies.txt}:/app/proxies.txt:ro expose: - "8003" command: [ "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8003", "--workers", "1", "--loop", "uvloop", "--http", "httptools", "--proxy-headers", "--forwarded-allow-ips", "${TRUSTED_PROXY_CIDRS}", "--timeout-keep-alive", "30", "--timeout-graceful-shutdown", "30", "--limit-concurrency", "256", "--backlog", "2048" ] networks: - yt_music_proxy tmpfs: - /tmp:size=256m,mode=1777 security_opt: - no-new-privileges:true cap_drop: - ALL pids_limit: 256 healthcheck: test: [ "CMD", "curl", "--fail", "--silent", "--show-error", "http://127.0.0.1:8003/healthz" ] interval: 30s timeout: 5s start_period: 20s retries: 3 logging: driver: json-file options: max-size: "10m" max-file: "5" # No Caddy labels here. # s1ne is now internal-only; Nginx is its only HTTP caller. nginx: image: nginx:alpine container_name: s1ne-nginx restart: unless-stopped depends_on: s1ne: condition: service_healthy networks: - yt_music_proxy volumes: # Full main config, not /etc/nginx/conf.d/default.conf. - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./static:/usr/share/nginx/html:ro tmpfs: - /var/cache/nginx:size=64m - /var/run:size=8m security_opt: - no-new-privileges:true pids_limit: 128 healthcheck: test: [ "CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1/healthz || exit 1" ] interval: 30s timeout: 5s start_period: 10s retries: 3 logging: driver: json-file options: max-size: "10m" max-file: "5" labels: # Caddy routes the public domain to Nginx port 80. caddy: ${APP_DOMAIN} caddy_ingress_network: yt_music_proxy caddy.encode: zstd gzip caddy.request_body.max_size: 256KB caddy.header.-Server: "" caddy.header.X-Content-Type-Options: "nosniff" caddy.header.X-Frame-Options: "DENY" caddy.header.Referrer-Policy: "strict-origin-when-cross-origin" caddy.header.Permissions-Policy: "camera=(), microphone=(), geolocation=()" caddy.header.Strict-Transport-Security: "max-age=31536000" caddy.reverse_proxy: "{{upstreams 80}}" volumes: yt_music_data: networks: yt_music_proxy: external: true