Media Stack
Stack média automatisée pour NAS Synology — tu demandes un film sur ton téléphone, il est téléchargé, trié et dispo sur ta TV. Automatiquement.
Le concept
Tu demandes un film sur ton téléphone → il est téléchargé, trié et disponible sur ta TV. Automatiquement. Pas d'intervention manuelle, pas de rangement à faire. Tout est orchestré par Docker sur un NAS Synology.
La stack est 100% open source, sans abonnement, et optimisée pour ne pas dupliquer les fichiers sur le disque grâce aux hardlinks.
Architecture globale
Tout tourne dans des conteneurs Docker interconnectés, sécurisés par un VPN :
┌─────────────────────────────────────────────────────────────────┐
│ NAS SYNOLOGY (Docker) │
│ │
│ ┌───────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Jellyseerr│───▶│ Radarr │───▶│ │ │ │ │
│ │ (Requêtes)│ │ (Films) │ │ │ │ │ │
│ └───────────┘ └──────────┘ │ │ │ Gluetun │ │
│ │qBittorrent├─▶│ (VPN) │──┼─▶ Internet
│ ┌───────────┐ ┌──────────┐ │ │ │ │ │
│ │ Prowlarr │───▶│ Sonarr │───▶│ │ │ │ │
│ │(Indexeurs) │ │ (Séries) │ │ │ └──────────┘ │
│ └─────┬─────┘ └──────────┘ └────┬─────┘ │
│ │ │ Hardlink │
│ ┌─────┴──────┐ ┌─────▼─────┐ │
│ │ Jackett + │ │ /data/ │ │
│ │FlareSolverr│ │ media/ │ │
│ └────────────┘ └─────┬─────┘ │
│ │ │
│ ┌──────────┐ ┌──────▼─────┐ │
│ │ Homarr │ │ Jellyfin │ │
│ │(Dashboard)│ │ (Streaming) │ │
│ └──────────┘ └────────────┘ │
└────────────────────────┬────────────────────────────────────── ┘
│
┌──────────▼───────────┐
│ Nginx Proxy Manager │
│ (Reverse Proxy) │
└──────────┬───────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
stream.victor request.victor IP NAS locale
karcher.com karcher.com (réseau local)
Les services en détail
| Service | Rôle | Port local |
|---|---|---|
| Jellyfin | Le "Netflix" personnel — streaming de tous tes médias | 8096 |
| Jellyseerr | Portail de requêtes — tes potes demandent un film ici | 5055 |
| Radarr | Automatisation des films — recherche, download, classement | 7878 |
| Sonarr | Automatisation des séries — idem pour les séries TV | 8989 |
| Prowlarr | Gestion centralisée de tous les indexeurs/trackers | 9696 |
| qBittorrent | Client torrent — télécharge les fichiers | 8080 |
| Gluetun | VPN Wireguard avec kill switch intégré | — |
| Jackett + FlareSolverr | Bypass Cloudflare pour certains trackers | 9117 / 8191 |
| Nginx Proxy Manager | Reverse proxy HTTPS pour l'accès externe | 80 / 443 / 81 |
| Homarr | Dashboard centralisé pour tout voir d'un coup | 7575 |
| Watchtower | Mise à jour automatique des conteneurs | — |
Le flux d'un téléchargement
Voici ce qui se passe quand quelqu'un demande un film :
TON POTE SERVEUR NAS
──────── ───────────
"Je veux Dune 2"
│
▼
┌──────────┐
│Jellyseerr│──── Requête ────▶ ┌────────┐
└──────────┘ │ Radarr │
└───┬────┘
│ Recherche le meilleur
│ torrent disponible
▼
┌──────────┐
│ Prowlarr │── Cherche sur tes
└────┬─────┘ trackers privés
│
▼
┌────────────┐
│qBittorrent │── Télécharge
└─────┬──────┘ via VPN
│
│ Terminé !
▼
┌──────────┐
│ Radarr │── Renomme le fichier
└────┬─────┘ + Hardlink vers
│ /data/media/movies/
▼
┌──────────┐
│ Jellyfin │── Le film apparaît
└──────────┘ automatiquement !
Film dispo sur ta TV ✓
Temps total : de quelques minutes à quelques heures selon la taille du fichier et la vitesse de download.
Réseau et sécurité
Accès depuis l'extérieur
Pour que tes potes puissent accéder à Jellyfin et Jellyseerr depuis chez eux :
- IP fixe V4 Full-Stack — demandée depuis l'espace abonné Free (gratuit)
- Ports 80 et 443 — redirigés vers l'IP locale du NAS dans les settings Freebox
- Nginx Proxy Manager — fait le reverse proxy HTTPS vers les bons conteneurs
Résultat : stream.victorkarcher.com pointe vers Jellyfin, request.victorkarcher.com vers Jellyseerr.
Le VPN (Gluetun) et le Kill Switch
Tout le trafic P2P passe par le VPN Wireguard. C'est non négociable.
qBittorrent ── network_mode: service:gluetun ──▶ Gluetun ──▶ Internet
│
KILL SWITCH
Si Gluetun tombe,
qBit perd le réseau
= zéro fuite
Le secret : network_mode: service:gluetun dans le docker-compose.yml. qBittorrent n'a pas son propre réseau — il utilise celui de Gluetun. Si le VPN coupe, le conteneur torrent n'a plus d'internet du tout.
Dépannage : module "tun" manquant
Après un redémarrage du NAS, Gluetun peut afficher no such file or directory /dev/net/tun. C'est parce que Synology ne crée pas automatiquement le device TUN. Fix en SSH :
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 600 /dev/net/tun
sudo docker-compose up -d
Astuce : tu peux ajouter ça dans un script au démarrage du NAS (Panneau de configuration > Planificateur de tâches) pour ne plus jamais avoir à le faire manuellement.
Téléchargement et indexation
Prowlarr — le chef d'orchestre des trackers
Prowlarr centralise tous tes trackers privés et les distribue automatiquement à Radarr et Sonarr. Tu ajoutes un tracker une fois dans Prowlarr, et tous les *arr en profitent.
Bypass Cloudflare pour certains trackers
Certains trackers sont protégés par Cloudflare et bloquent les requêtes automatisées de Prowlarr. On contourne avec une chaîne spéciale :
Tracker protégé (Cloudflare)
│
▼
FlareSolverr ── Simule un vrai navigateur
│ pour passer Cloudflare
▼
Jackett ────── Extrait les résultats
│ de recherche
▼
Prowlarr ────▶ Radarr / Sonarr
Config Docker du bypass :
flaresolverr:
image: 21hsmw/flaresolverr:nodriver
environment:
- DRIVER=nodriver
- TZ=Europe/Paris
Important : utilise bien l'image 21hsmw/flaresolverr:nodriver — c'est la seule qui fonctionne avec les dernières protections Cloudflare.
Le système de fichiers (Hardlinks)
Pourquoi c'est important
Règle d'or : jamais de déplacement (Move), toujours des Hardlinks.
Un hardlink crée un deuxième chemin vers le même fichier physique sur le disque. Le fichier n'existe qu'une seule fois :
/data/torrents/complete/Dune.2.2024.mkv ◄──┐
├── Même fichier sur disque
/data/media/movies/Dune 2 (2024).mkv ◄──┘ (pas de copie !)
Les avantages
- Zéro duplication — un film de 50 Go ne prend que 50 Go, même s'il est "à deux endroits"
- Seeding continue — le fichier torrent original reste intact pour maintenir ton ratio
- Instantané — pas de temps de copie, le hardlink se crée en millisecondes
Configuration dans Radarr/Sonarr
Pour que les hardlinks fonctionnent, tous les chemins doivent être dans le même volume Docker /data/ :
- Téléchargement :
/data/torrents/complete/ - Médiathèque films :
/data/media/movies/ - Médiathèque séries :
/data/media/tv/
Dans les settings Radarr/Sonarr → "Media Management" → Root Folder : toujours naviguer dans /data/media/... (pas /movies/ ou un autre montage).
Import manuel
Si tu as téléchargé des fichiers en dehors de la stack :
- Ajouter le film/série dans Radarr/Sonarr (même en "Non Surveillé")
- Aller dans Wanted > Manual Import
- Scanner le dossier de téléchargement
- Vérifier que le mode est sur "Copy/Hardlink" (en bas à gauche, pas "Move" !)
- Valider l'import
Installation
Prérequis
- Un NAS Synology (ou tout serveur Linux avec Docker et Docker Compose)
- Accès SSH activé sur le NAS
- Un fournisseur VPN compatible Wireguard (Mullvad, ProtonVPN, etc.)
Étape par étape
# 1. Se connecter en SSH au NAS
ssh ton-user@IP-DU-NAS
# 2. Cloner le projet
cd /volume1
git clone https://github.com/vkarcher/media-stack.git
cd media-stack
# 3. Copier et éditer la configuration
cp .env.example .env
vi .env
# → Modifier : TZ, PUID, PGID, config VPN Wireguard
# 4. Préparer les dossiers et les permissions
sudo bash setup.sh
# Crée /data/torrents/, /data/media/, etc.
# et applique les bonnes permissions
# 5. Démarrer toute la stack
sudo docker-compose up -d
# 6. Vérifier que tout tourne
sudo docker-compose ps
Attendre quelques minutes que tous les conteneurs démarrent. Commencer par configurer Homarr pour avoir un dashboard centralisé.
Accès aux services
Une fois la stack lancée, tout est accessible via l'IP locale du NAS :
| Service | URL | Première chose à faire |
|---|---|---|
| Homarr | http://IP-NAS:7575 | Configurer les widgets et raccourcis |
| Jellyfin | http://IP-NAS:8096 | Créer un compte admin + ajouter les bibliothèques |
| Jellyseerr | http://IP-NAS:5055 | Connecter à Radarr et Sonarr |
| Radarr | http://IP-NAS:7878 | Ajouter les profils de qualité FR |
| Sonarr | http://IP-NAS:8989 | Idem pour les séries |
| Prowlarr | http://IP-NAS:9696 | Ajouter tes trackers privés |
| qBittorrent | http://IP-NAS:8080 | Login par défaut : admin / voir les logs |
Guide de survie SSH
Le problème le plus fréquent
qBittorrent refuse de redémarrer après une mise à jour de Gluetun, ou les conteneurs ne se voient plus entre eux.
Cause : les réseaux Docker virtuels sont devenus "fantômes".
Fix : un simple docker restart ne suffit pas. Il faut détruire et recréer les réseaux :
# 1. Tout éteindre proprement
sudo docker-compose down
# 2. Tout reconstruire et rallumer
sudo docker-compose up -d
Vérifier l'état de la stack
# Voir tous les conteneurs et leur état
sudo docker-compose ps
# Voir les logs d'un conteneur spécifique
sudo docker-compose logs -f gluetun
sudo docker-compose logs -f radarr
# Vérifier que le VPN fonctionne
sudo docker exec gluetun wget -qO- ifconfig.me
# → Doit afficher l'IP du VPN, pas ton IP Free
Mettre à jour les conteneurs
# Télécharger les nouvelles images
sudo docker-compose pull
# Redémarrer avec les nouvelles versions
sudo docker-compose up -d
Les données et configurations sont persistées dans des volumes Docker — la mise à jour ne perd rien.
Images Docker utilisées
| Service | Image | Notes |
|---|---|---|
| Gluetun | qmcgaw/gluetun | VPN Wireguard, nécessite NET_ADMIN + /dev/net/tun |
| qBittorrent | lscr.io/linuxserver/qbittorrent:latest | Utilise network_mode: service:gluetun |
| FlareSolverr | 21hsmw/flaresolverr:nodriver | Version modifiée, seule compatible Cloudflare récent |
| Jackett | lscr.io/linuxserver/jackett:latest | Interface avec FlareSolverr pour les trackers protégés |
| Prowlarr | lscr.io/linuxserver/prowlarr:latest | Distribue les indexeurs à tous les *arr |
| Radarr | lscr.io/linuxserver/radarr:latest | Monte /volume1:/volume1 pour les hardlinks |
| Sonarr | lscr.io/linuxserver/sonarr:latest | Idem |
| Jellyfin | lscr.io/linuxserver/jellyfin:latest | Médiathèque dans /data/media |
| Jellyseerr | fallenbagel/jellyseerr:latest | Portail de requêtes |
| NPM | jc21/nginx-proxy-manager:latest | Reverse proxy, ports 80/443/81 |
| Homarr | ghcr.io/ajnart/homarr:latest | Dashboard centralisé |
| Watchtower | containrrr/watchtower | MAJ auto des conteneurs, cleanup anciennes images |
Structure des volumes
Tous les conteneurs persistent leur config dans /volume1/docker/<service>/. Les médias et torrents sont dans un volume partagé :
/volume1/
├── docker/
│ ├── gluetun/
│ ├── qbittorrent/
│ ├── jackett/
│ ├── prowlarr/
│ ├── radarr/
│ ├── sonarr/
│ ├── jellyfin/
│ ├── jellyseerr/
│ ├── npm/
│ │ ├── data/
│ │ └── letsencrypt/
│ └── homarr/
│ ├── configs/
│ ├── icons/
│ └── data/
├── torrents/ ← qBittorrent télécharge ici
└── media/ ← Jellyfin lit ici (hardlinks depuis torrents/)
Variables d'environnement communes
Tous les conteneurs LinuxServer.io utilisent les mêmes variables :
PUID=1000/PGID=1000— UID/GID de l'utilisateur NASTZ=Europe/Paris— Timezone
Pour Gluetun, il faut ajouter :
VPN_SERVICE_PROVIDER— ton fournisseur (protonvpn, mullvad, etc.)VPN_TYPE=wireguardWIREGUARD_PRIVATE_KEY— ta clé privée WireguardWIREGUARD_ADDRESSES— ton IP Wireguard
Docker Compose complet
version: "3.8"
services:
# ── VPN & TÉLÉCHARGEMENT ──────────────────────
gluetun:
image: qmcgaw/gluetun
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=custom
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=TA_CLE_PRIVEE_ICI
- WIREGUARD_ADDRESSES=TON_IP_WIREGUARD_ICI
ports:
- 8080:8080 # qBittorrent Web UI
- 6881:6881 # qBittorrent écoute (TCP)
- 6881:6881/udp
volumes:
- /volume1/docker/gluetun:/gluetun
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
- WEBUI_PORT=8080
volumes:
- /volume1/docker/qbittorrent:/config
- /volume1/torrents:/data/torrents
depends_on:
- gluetun
restart: unless-stopped
# ── RECHERCHE & INDEXEURS ─────────────────────
flaresolverr:
image: 21hsmw/flaresolverr:nodriver
container_name: flaresolverr
environment:
- TZ=Europe/Paris
- DRIVER=nodriver
ports:
- 8191:8191
restart: unless-stopped
jackett:
image: lscr.io/linuxserver/jackett:latest
container_name: jackett
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
ports:
- 9117:9117
volumes:
- /volume1/docker/jackett:/config
- /volume1/torrents:/downloads
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
ports:
- 9696:9696
volumes:
- /volume1/docker/prowlarr:/config
restart: unless-stopped
# ── GESTIONNAIRES DE MÉDIAS (*ARR) ────────────
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
ports:
- 7878:7878
volumes:
- /volume1/docker/radarr:/config
- /volume1:/volume1
restart: unless-stopped
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
ports:
- 8989:8989
volumes:
- /volume1/docker/sonarr:/config
- /volume1:/volume1
restart: unless-stopped
# ── STREAMING & REQUÊTES ──────────────────────
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
container_name: jellyfin
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
ports:
- 8096:8096
volumes:
- /volume1/docker/jellyfin:/config
- /volume1/media:/data/media
restart: unless-stopped
jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: jellyseerr
environment:
- TZ=Europe/Paris
ports:
- 5055:5055
volumes:
- /volume1/docker/jellyseerr:/app/config
restart: unless-stopped
# ── SYSTÈME & ACCÈS EXTERNE ───────────────────
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
ports:
- 80:80 # HTTP
- 443:443 # HTTPS (SSL)
- 81:81 # Admin NPM
volumes:
- /volume1/docker/npm/data:/data
- /volume1/docker/npm/letsencrypt:/etc/letsencrypt
restart: unless-stopped
homarr:
image: ghcr.io/ajnart/homarr:latest
container_name: homarr
ports:
- 7575:7575
volumes:
- /volume1/docker/homarr/configs:/app/data/configs
- /volume1/docker/homarr/icons:/app/public/icons
- /volume1/docker/homarr/data:/data
restart: unless-stopped
watchtower:
image: containrrr/watchtower
container_name: watchtower
environment:
- TZ=Europe/Paris
- WATCHTOWER_CLEANUP=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped