229 lines
6.5 KiB
YAML
229 lines
6.5 KiB
YAML
version: "3.9"
|
|
|
|
services:
|
|
# Jellyfin Media Server (Plex alternative - no license needed)
|
|
jellyfin:
|
|
image: jellyfin/jellyfin:latest
|
|
container_name: jellyfin
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/Berlin
|
|
volumes:
|
|
- ./config/jellyfin:/config
|
|
- ./cache/jellyfin:/cache
|
|
- /mnt/r2-media:/media:ro # R2 mounted via rclone
|
|
ports:
|
|
- "8096:8096"
|
|
- "8920:8920" # HTTPS
|
|
networks:
|
|
- media-network
|
|
depends_on:
|
|
- r2-mount
|
|
labels:
|
|
- "traefik.enable=true"
|
|
# HTTP router (redirects to HTTPS)
|
|
- "traefik.http.routers.jellyfin.rule=Host(`movies.jeffemmett.com`)"
|
|
- "traefik.http.routers.jellyfin.entrypoints=web"
|
|
- "traefik.http.routers.jellyfin.middlewares=jellyfin-https-redirect"
|
|
# HTTPS router
|
|
- "traefik.http.routers.jellyfin-secure.rule=Host(`movies.jeffemmett.com`)"
|
|
- "traefik.http.routers.jellyfin-secure.entrypoints=websecure"
|
|
- "traefik.http.routers.jellyfin-secure.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.jellyfin-secure.middlewares=jellyfin-headers"
|
|
# Service
|
|
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
|
|
# Middleware: HTTPS redirect
|
|
- "traefik.http.middlewares.jellyfin-https-redirect.redirectscheme.scheme=https"
|
|
- "traefik.http.middlewares.jellyfin-https-redirect.redirectscheme.permanent=true"
|
|
# Middleware: Headers for proper session handling
|
|
- "traefik.http.middlewares.jellyfin-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
|
|
- "traefik.http.middlewares.jellyfin-headers.headers.customRequestHeaders.X-Forwarded-Host=movies.jeffemmett.com"
|
|
- "traefik.http.middlewares.jellyfin-headers.headers.stsSeconds=31536000"
|
|
- "traefik.http.middlewares.jellyfin-headers.headers.stsIncludeSubdomains=true"
|
|
|
|
# R2 Mount Service (rclone FUSE mount)
|
|
r2-mount:
|
|
image: rclone/rclone:latest
|
|
container_name: r2-mount
|
|
restart: unless-stopped
|
|
cap_add:
|
|
- SYS_ADMIN
|
|
devices:
|
|
- /dev/fuse
|
|
security_opt:
|
|
- apparmor:unconfined
|
|
environment:
|
|
- RCLONE_CONFIG=/config/rclone/rclone.conf
|
|
volumes:
|
|
- ./config/rclone:/config/rclone:ro
|
|
- /mnt/r2-media:/mnt/r2-media:shared
|
|
- ./cache/rclone:/cache
|
|
command: >
|
|
mount r2:plex-media /mnt/r2-media
|
|
--allow-other
|
|
--allow-non-empty
|
|
--vfs-cache-mode full
|
|
--vfs-cache-max-size 50G
|
|
--vfs-cache-max-age 72h
|
|
--vfs-read-chunk-size 128M
|
|
--vfs-read-chunk-size-limit 1G
|
|
--buffer-size 512M
|
|
--dir-cache-time 72h
|
|
--poll-interval 15s
|
|
--log-level INFO
|
|
--cache-dir /cache
|
|
networks:
|
|
- media-network
|
|
|
|
# Sonarr - TV Show Management
|
|
sonarr:
|
|
image: linuxserver/sonarr:latest
|
|
container_name: sonarr
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/Berlin
|
|
volumes:
|
|
- ./config/sonarr:/config
|
|
- /mnt/r2-media/tv:/tv
|
|
- ./downloads:/downloads
|
|
ports:
|
|
- "8989:8989"
|
|
networks:
|
|
- media-network
|
|
depends_on:
|
|
- r2-mount
|
|
|
|
# Radarr - Movie Management
|
|
radarr:
|
|
image: linuxserver/radarr:latest
|
|
container_name: radarr
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/Berlin
|
|
volumes:
|
|
- ./config/radarr:/config
|
|
- /mnt/r2-media/movies:/movies
|
|
- ./downloads:/downloads
|
|
ports:
|
|
- "7878:7878"
|
|
networks:
|
|
- media-network
|
|
depends_on:
|
|
- r2-mount
|
|
|
|
# Prowlarr - Indexer Management
|
|
prowlarr:
|
|
image: linuxserver/prowlarr:latest
|
|
container_name: prowlarr
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/Berlin
|
|
volumes:
|
|
- ./config/prowlarr:/config
|
|
ports:
|
|
- "9696:9696"
|
|
networks:
|
|
- media-network
|
|
|
|
# Transmission - Download Client
|
|
transmission:
|
|
image: linuxserver/transmission:latest
|
|
container_name: transmission
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/Berlin
|
|
- TRANSMISSION_WEB_HOME=/web
|
|
volumes:
|
|
- ./config/transmission:/config
|
|
- ./downloads:/downloads
|
|
- ./watch:/watch
|
|
ports:
|
|
- "9091:9091"
|
|
- "51413:51413"
|
|
- "51413:51413/udp"
|
|
networks:
|
|
- media-network
|
|
|
|
# R2 Sync Service - Moves completed downloads to R2
|
|
r2-sync:
|
|
build:
|
|
context: ./services/r2-sync
|
|
dockerfile: Dockerfile
|
|
container_name: r2-sync
|
|
restart: unless-stopped
|
|
environment:
|
|
- R2_ACCOUNT_ID=${R2_ACCOUNT_ID}
|
|
- R2_ACCESS_KEY=${R2_ACCESS_KEY}
|
|
- R2_SECRET_KEY=${R2_SECRET_KEY}
|
|
- R2_BUCKET=plex-media
|
|
- WATCH_DIR=/downloads/complete
|
|
- SYNC_INTERVAL=300
|
|
volumes:
|
|
- ./downloads/complete:/downloads/complete:ro
|
|
- ./config/rclone:/config/rclone:ro
|
|
- ./logs/r2-sync:/logs
|
|
networks:
|
|
- media-network
|
|
|
|
# Cost Monitor - Tracks R2 storage costs and exposes Prometheus metrics
|
|
cost-monitor:
|
|
build:
|
|
context: ./services/cost-monitor
|
|
dockerfile: Dockerfile
|
|
container_name: cost-monitor
|
|
restart: unless-stopped
|
|
environment:
|
|
- R2_BUCKET=plex-media
|
|
- RCLONE_CONFIG=/config/rclone/rclone.conf
|
|
- METRICS_PORT=9100
|
|
- UPDATE_INTERVAL=3600
|
|
volumes:
|
|
- ./config/rclone:/config/rclone:ro
|
|
ports:
|
|
- "9100:9100"
|
|
networks:
|
|
- media-network
|
|
|
|
# Traefik Reverse Proxy
|
|
traefik:
|
|
image: traefik:v3.0
|
|
container_name: traefik
|
|
restart: unless-stopped
|
|
command:
|
|
- "--api.dashboard=true"
|
|
- "--providers.docker=true"
|
|
- "--providers.docker.exposedbydefault=false"
|
|
- "--entrypoints.web.address=:80"
|
|
- "--entrypoints.websecure.address=:443"
|
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
|
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
|
- "--certificatesresolvers.letsencrypt.acme.email=jeff@jeffemmett.com"
|
|
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- ./config/traefik/letsencrypt:/letsencrypt
|
|
networks:
|
|
- media-network
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.traefik.rule=Host(`traefik.jeffemmett.com`)"
|
|
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.traefik.service=api@internal"
|
|
|
|
networks:
|
|
media-network:
|
|
driver: bridge
|