Caution
Disclaimer: The author and contributors do not claim ownership of any services listed or used in this repository and are not legally responsible for any improper or illegal use. It is provided for educational purposes only. The repository does not endorse piracy or copyright infringement. Creating a media platform based on torrents may involve downloading copyrighted content, which, without proper authorization, may be illegal in many jurisdictions. All rights go to the owners of the software used.
This is my Docker setup for a complete media server.
The primary method now uses two Docker Compose files:
docker-compose-internal.ymlfor internal automation and VPN-bound servicesdocker-compose-public.ymlfor user-facing services
I switched from transmission-openvpn to qbittorrent + gluetun because:
- VPN and torrent client are now independent and easier to maintain
- Gluetun is actively maintained and more flexible
- qBittorrent is better for large torrent libraries (tags, categories, priorities, queueing)
- Internal updates can be done without disrupting Jellyfin/Seerr users
Note
I've also created a chill-extra folder that can send WhatsApp notifications when media is added in Jellyfin, add removarr, and provide helper scripts.
Important
The legacy method (single compose + transmission-openvpn) is still available in this guide.
- Home Server
- License
- docker
- docker-compose
- a server (tested on Synology DS923+ and a custom Linux server)
- create a dedicated docker user
Synology:
- Install Docker/Container Manager from Synology Package Center
- Create a docker user
- then run
id <username>and noteuid/gidasPUID/PGIDfor your.env
- then run
- Install latest compose plugin if needed
- Check version:
docker compose version(ordocker-compose version)
docker-compose-internal.yml:
docker-compose-public.yml:
Create folders:
mkdir -p /opt/chill/{qbit,prowlarr,radarr,sonarr,jellyfin,seerr}/config
mkdir -p /opt/chill/storage/downloads/{watch,completed,incomplete,medias/{movies,series}}Download files in /opt/chill/:
cd /opt/chill
wget https://raw.githubusercontent.com/garnajee/home-server/master/docker-compose-internal.yml -O docker-compose-internal.yml
wget https://raw.githubusercontent.com/garnajee/home-server/master/docker-compose-public.yml -O docker-compose-public.yml
wget https://raw.githubusercontent.com/garnajee/home-server/master/.env-example -O .envYou usually only need to edit .env:
WG_PRV_KEYWG_SERVER_COUNTRIESQBIT_WEBUI_PORTBASE,DOWNLOADSPUID,PGIDTZNETWORKIP(detect withip route | awk '!/ (docker0|br-)/ && /src/ {print $1}')
Example:
# VPN
WG_PRV_KEY="YOUR_WIREGUARD_PRIVATE_KEY"
WG_SERVER_COUNTRIES="Country"
QBIT_WEBUI_PORT=8088
# the rest is unchanged
BASE=/opt/chill
DOWNLOADS=/opt/chill/storage/downloads
PUID=1030
PGID=100
TZ=Europe/Paris
NETWORKIP=192.168.1.0/24Optional (it can be auto-created), but manual creation is recommended:
docker network rm net-chill
docker network create net-chill -d bridge --subnet 10.10.66.0/24Run internal stack:
cd /opt/chill
docker compose -f docker-compose-internal.yml up -dRun public stack:
docker compose -f docker-compose-public.yml up -dCheck containers:
docker compose -f docker-compose-internal.yml ps -a
docker compose -f docker-compose-public.yml ps -adocker exec -it gluetun wget -qO- https://ifconfig.coThen check the location of that IP (for example on ifconfig.co).
Update internal stack only:
docker compose -f docker-compose-internal.yml pull
docker compose -f docker-compose-internal.yml up -dUpdate public stack only:
docker compose -f docker-compose-public.yml pull
docker compose -f docker-compose-public.yml up -d| Service | Address |
|---|---|
| qBittorrent WebUI | <IP>:${QBIT_WEBUI_PORT} |
| Prowlarr | <IP>:8001 |
| Jellyfin | <IP>:8003 |
| Seerr | <IP>:8004 |
| Radarr | <IP>:8010 |
| Sonarr | <IP>:8011 |
This method is still supported.
cd /opt/chill
wget https://raw.githubusercontent.com/garnajee/home-server/master/docker-compose-medias.yml -O docker-compose-medias.yml
wget https://raw.githubusercontent.com/garnajee/home-server/master/.env-example-legacy -O .env
docker compose -f docker-compose-medias.yml up -dThis is no longer the recommended default.
To access Jellyfin and Seerr outside your local network, use a reverse proxy. In this guide, we use Nginx Proxy Manager.
You need to open 2 ports on your router:
| Application/Service | Internal Port | External Port | Protocol | Equipment |
|---|---|---|---|---|
| HTTP | 8080 | 80 | TCP/UDP | Your-Server |
| HTTPS | 4443 | 443 | TCP/UDP | Your-Server |
Create folders:
mkdir -p /opt/docker/{nginx-proxy-manager,npm-db}Download compose file:
cd /opt/docker
wget https://raw.githubusercontent.com/garnajee/home-server/master/docker-compose-rp.yml -O docker-compose.ymlcd /opt/docker
docker compose up -dService URL: <IP>:81
Default admin:
email: admin@example.com
password: changemeI use OVH:
- create an account on OVH
- buy a domain name
- create the token for SSL automation:
You'll get values like:
dns_ovh_endpoint = ovh-eu
dns_ovh_application_key = xxxxxxxxxxxxx
dns_ovh_application_secret = xxxxxxxxxxxxx
dns_ovh_consumer_key = xxxxxxxxxxxxxIn NPM:
- go to
SSL Certificates - add wildcard + root domain:
*.yourdomain.comandyourdomain.com
Configure categories, tags, queueing, and priorities to match your workflow.
If users run into issues, they can compare with this known working reference:
Follow TRaSH guides.
If you want similar quality profiles/custom formats, see recyclarr-setup.
Add indexers, then connect apps in Settings > Apps:
- Prowlarr:
http://10.10.66.100:9696 - Radarr:
http://10.10.66.110:7878 - Sonarr:
http://10.10.66.111:8989
Follow initial setup wizard and create users.
For custom menu links:
- ${BASE}/jellyfin/web-config.json:/usr/share/jellyfin/web/config.jsonRead more: Jellyfin web config.
Sign in with Jellyfin account and use internal Docker IPs for Jellyfin/Radarr/Sonarr connections.
Do not want to expose ports?
- Official Synology VPN package (requires router port-forwarding)
- Tailscale on Synology
- CloudFlare Tunnel
Warning
Be careful with CloudFlare Tunnel for Jellyfin streaming, it can violate Cloudflare ToS.
Jellyfin webhook templates are available for:
- Generic API:
webhooks/jellyfin/global-item.handlebars - Discord:
webhooks/jellyfin/discord - Microsoft Teams:
webhooks/jellyfin/ms-teams
Install plugin first: Jellyfin Dashboard > Plugins > Catalog > Webhook.
Restart Jellyfin:
cd /opt/chill
docker compose -f docker-compose-public.yml restart jellyfinFor WhatsApp workflow examples, see whatsapp-api.
If you need to fake upload ratio on private trackers, you can use Ratio.py.
Caution
Not recommended for long-term usage. Consider seeding fairly.
When using the legacy Transmission container, you can run the script there so traffic stays behind VPN.
This project is under MIT License.