s1ne/docker-compose.yml
2026-09-09 06:28:04 +00:00

164 lines
4.2 KiB
YAML

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