At home, I have a Keenetic router equipped with a 2TB USB drive. For quite some time, it functioned merely as a network storage solution—files were stored, and Transmission was running smoothly. However, the default Transmission interface felt like a throwback to 2009, making it cumbersome to manage from my phone, and watching downloaded content on the television was a quest in itself. One evening, I decided to address these issues. The outcome was the deployment of three Docker containers, all initiated with a single command, transforming the experience into something much more user-friendly:
- Open Telegram and send the bot a
.torrent file.
- The bot confirms: “✅ Added!”
- After a while, it notifies: “✅ Downloaded! 📁 Movie Title · 💾 15 GB”
- I open Jellyfin on the TV— the movie is already there, complete with a poster and a description in Russian.
Let me explain how this setup works.
What You Will Achieve
- Transmission + Flood UI — a torrent client with a modern interface replacing the default one.
- Jellyfin — a media server featuring posters, descriptions, and subtitles, accessible on the TV, phone, or browser.
- Telegram Bot — add torrents and receive notifications directly in the messenger.
- Watch Folder — simply drop a
.torrent file into a folder on the NAS, and it downloads automatically.
- All data is stored on a NAS network drive (SMB/CIFS), ensuring persistence through OS reinstalls.
Stack and Architecture
┌───────────────────────────────────────────────┐
│ Docker on Windows │
│ │
│ ┌───────────────────────────────────────────┐│
│ │ Transmission + Flood UI ││
│ │ :9091 ││
│ └───────────────────────────────────────────┘│
│ ┌───────────────────────────────────────────┐│
│ │ Jellyfin ││
│ │ :8096 ││
│ └───────────────────────────────────────────┘│
└───────────────────────────────────────────────┘
The key idea is that a CIFS-type Docker volume mounts the network share directly into the containers. Both containers (Transmission and Jellyfin) work with the same files on the NAS: the first writes, while the second reads.
Requirements
- Docker Desktop (Windows/macOS) or Docker Engine (Linux)
- A NAS or router with a USB drive and SMB share (Keenetic, Synology, QNAP, etc.)
- A Telegram account for the bot
If you don’t have a NAS, a FAQ section at the end provides alternatives for using a local folder.
Step 1: Clone the Repository
git clone https://github.com/vervs3/mediabox.git
cd mediabox
Project structure:
mediabox/
├── docker-compose.yml
├── .env.example
├── bot/
│ ├── bot.py
│ ├── Dockerfile
│ └── requirements.txt
└── transmission/
├── setup-flood.sh
└── custom-cont-init.d/
└── 01-fix-settings.sh
Step 2: Configure the Settings
cp .env.example .env
Edit the .env file:
# Timezone
TZ=Europe/Moscow
# Network disk settings (NAS, Keenetic router, etc.)
SMB_HOST=//192.168.1.45/Transmission
SMB_USER=admin
SMB_PASSWORD=your_password
# Telegram bot (create one with @BotFather)
BOT_TOKEN=123456789:AAxxxxx...
# Your Telegram ID (find it with @userinfobot)
ALLOWED_USER_ID=123456789
Step 3: Install Flood UI
Here’s where I encountered my first hurdle. I expected the linuxserver/transmission image to include Flood out of the box, as it used to. However, recent versions have removed third-party UIs, and upon starting the container, it displays:
Changes Required!
This image no longer bundles 3rd party Transmission UI packages.
We would advise you to use subfolders under /config to store your UI packages
Thus, I manually downloaded Flood:
# Linux / macOS
bash transmission/setup-flood.sh
The script fetches the latest release of flood-for-transmission and unpacks it into transmission/config/flood-ui/. For Windows users, download the archive manually from GitHub releases and extract it into the same folder.
Step 4: Launch the Containers
docker compose up -d
On the first run, Docker will download the images (approximately 700 MB in total) and start all three services. To verify:
docker compose ps
NAME STATUS
transmission Up
jellyfin Up
transmission-bot Up
How It Works Internally — Three Noteworthy Points
1. Docker Volume with CIFS Instead of Bind Mount
The first approach I tried was to pass the network drive Z: as a standard volume:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
This did not work. Docker Desktop on Windows operates on top of WSL2, and mounted network drives (Z:, servershare) are inaccessible from within the container. WSL2 simply does not recognize them. The solution is to use a Docker volume with the CIFS driver. Docker mounts the share directly, bypassing Windows:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
Both containers—Transmission and Jellyfin—connect to this volume and operate on the same files. Transmission writes to /downloads/Downloads, while Jellyfin reads from /media/Downloads (both refer to the same volume but under different names within the containers).
2. Patching Transmission Settings on Each Start
The second surprise was that the linuxserver/transmission image resets part of the settings to default values every time it starts. Attempting to set watch-dir through environment variables does not work. Editing settings.json results in it being reset upon the next start. The custom-cont-init.d mechanism helps here: scripts in this folder execute after the image initialization but before transmission-daemon starts. We place our patch there:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
The logs confirm that the script executed successfully:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
3. Git Bash Breaks Paths in Environment Variables
The third pitfall is specific to Windows + Git Bash. When passing a path like /flood-for-transmission/ through -e in docker run, Git Bash converts it to C:/Program Files/Git/flood-for-transmission/. In the logs, this appears as:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
This can be resolved by setting an environment variable before the command:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
In docker-compose.yml, this issue does not arise since paths do not pass through the Git Bash interpreter.
Telegram Bot
The bot operates within a container using network_mode: "service:transmission", sharing the network stack with Transmission and connecting to it via localhost:9091. This eliminates the need to open additional ports and reduces network latency between containers.
Commands
| Command |
Function |
/list |
Displays a list of all torrents with progress bars and control buttons. |
/active |
Shows only active downloads. |
/stats |
Displays speed and overall statistics. |
/help |
Provides help information. |
Adding Torrents
There are three methods to add torrents:
- Send a
.torrent file directly to the bot in chat.
- Send a
magnet: link as text.
- Drop a
.torrent file into the watch folder on the NAS—Transmission will pick it up automatically.
Unicode Progress Bar
A small yet delightful feature is that the progress is displayed directly in the message text:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
Completion Notifications Without Duplicates
To prevent the bot from spamming completed torrents upon restart, we initialize a list of already downloaded IDs at startup:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
When a torrent finishes downloading, the bot sends:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
Jellyfin — First Launch
- Open
http://localhost:8096
- Complete the setup wizard (language, create a user).
- Add a media library → type “Movies” → path
/media/Downloads
- Jellyfin will automatically find the files and download posters and descriptions from TMDB.
Connecting from TV or Phone
Install the Jellyfin app (available on Android TV, Apple TV, Roku, Fire TV, iOS, Android) and enter:
git clone https://github.com/vervs3/mediabox.git
cd mediabox
0
Where 192.168.x.x is the local IP of your computer (use ipconfig on Windows, ip a on Linux).
FAQ
Q: Does it work without a NAS, just on a local folder?
Yes. Replace the volume in docker-compose.yml:
mediabox/
├── docker-compose.yml
├── .env.example
├── bot/
│ ├── bot.py
│ ├── Dockerfile
│ └── requirements.txt
└── transmission/
├── setup-flood.sh
└── custom-cont-init.d/
└── 01-fix-settings.sh
Q: How to add a VPN so that the provider cannot see the torrents?
Add the gluetun service and switch Transmission to its network:
mediabox/
├── docker-compose.yml
├── .env.example
├── bot/
│ ├── bot.py
│ ├── Dockerfile
│ └── requirements.txt
└── transmission/
├── setup-flood.sh
└── custom-cont-init.d/
└── 01-fix-settings.sh
Q: How to update to new versions of the images?
mediabox/
├── docker-compose.yml
├── .env.example
├── bot/
│ ├── bot.py
│ ├── Dockerfile
│ └── requirements.txt
└── transmission/
├── setup-flood.sh
└── custom-cont-init.d/
└── 01-fix-settings.sh
Q: Can I access it externally, not just within the local network?
Yes, the easiest way is through Tailscale or Cloudflare Tunnel—without port forwarding on the router.
The stack is compact—three containers, one docker-compose.yml, and one .env file with passwords. It can be set up from scratch in five minutes, with files residing on the NAS, ensuring they remain intact during OS reinstalls.
If you have a router with a USB drive at home and are still watching movies through a file explorer, give this setup a try. The difference is palpable.
Repository: https://github.com/vervs3/mediabox. I would appreciate a star ⭐ and welcome any questions in the comments—especially if you encounter other hurdles with your hardware.
Домашний Netflix за вечер: Transmission + Jellyfin + Telegram-бот на Docker с поддержкой NAS
At home, I have a Keenetic router equipped with a 2TB USB drive. For quite some time, it functioned merely as a network storage solution—files were stored, and Transmission was running smoothly. However, the default Transmission interface felt like a throwback to 2009, making it cumbersome to manage from my phone, and watching downloaded content on the television was a quest in itself. One evening, I decided to address these issues. The outcome was the deployment of three Docker containers, all initiated with a single command, transforming the experience into something much more user-friendly:
.torrentfile.Let me explain how this setup works.
What You Will Achieve
.torrentfile into a folder on the NAS, and it downloads automatically.Stack and Architecture
The key idea is that a CIFS-type Docker volume mounts the network share directly into the containers. Both containers (Transmission and Jellyfin) work with the same files on the NAS: the first writes, while the second reads.
Requirements
If you don’t have a NAS, a FAQ section at the end provides alternatives for using a local folder.
Step 1: Clone the Repository
Project structure:
Step 2: Configure the Settings
Edit the
.envfile:Step 3: Install Flood UI
Here’s where I encountered my first hurdle. I expected the
linuxserver/transmissionimage to include Flood out of the box, as it used to. However, recent versions have removed third-party UIs, and upon starting the container, it displays:Thus, I manually downloaded Flood:
The script fetches the latest release of flood-for-transmission and unpacks it into
transmission/config/flood-ui/. For Windows users, download the archive manually from GitHub releases and extract it into the same folder.Step 4: Launch the Containers
On the first run, Docker will download the images (approximately 700 MB in total) and start all three services. To verify:
How It Works Internally — Three Noteworthy Points
1. Docker Volume with CIFS Instead of Bind Mount
The first approach I tried was to pass the network drive
Z:as a standard volume:0
This did not work. Docker Desktop on Windows operates on top of WSL2, and mounted network drives (
Z:,servershare) are inaccessible from within the container. WSL2 simply does not recognize them. The solution is to use a Docker volume with the CIFS driver. Docker mounts the share directly, bypassing Windows:0
Both containers—Transmission and Jellyfin—connect to this volume and operate on the same files. Transmission writes to
/downloads/Downloads, while Jellyfin reads from/media/Downloads(both refer to the same volume but under different names within the containers).2. Patching Transmission Settings on Each Start
The second surprise was that the
linuxserver/transmissionimage resets part of the settings to default values every time it starts. Attempting to setwatch-dirthrough environment variables does not work. Editingsettings.jsonresults in it being reset upon the next start. Thecustom-cont-init.dmechanism helps here: scripts in this folder execute after the image initialization but before transmission-daemon starts. We place our patch there:0
The logs confirm that the script executed successfully:
0
3. Git Bash Breaks Paths in Environment Variables
The third pitfall is specific to Windows + Git Bash. When passing a path like
/flood-for-transmission/through-eindocker run, Git Bash converts it toC:/Program Files/Git/flood-for-transmission/. In the logs, this appears as:0
This can be resolved by setting an environment variable before the command:
0
In
docker-compose.yml, this issue does not arise since paths do not pass through the Git Bash interpreter.Telegram Bot
The bot operates within a container using
network_mode: "service:transmission", sharing the network stack with Transmission and connecting to it vialocalhost:9091. This eliminates the need to open additional ports and reduces network latency between containers.Commands
/list/active/stats/helpAdding Torrents
There are three methods to add torrents:
.torrentfile directly to the bot in chat.magnet:link as text..torrentfile into thewatchfolder on the NAS—Transmission will pick it up automatically.Unicode Progress Bar
A small yet delightful feature is that the progress is displayed directly in the message text:
0
Completion Notifications Without Duplicates
To prevent the bot from spamming completed torrents upon restart, we initialize a list of already downloaded IDs at startup:
0
When a torrent finishes downloading, the bot sends:
0
Jellyfin — First Launch
http://localhost:8096/media/DownloadsConnecting from TV or Phone
Install the Jellyfin app (available on Android TV, Apple TV, Roku, Fire TV, iOS, Android) and enter:
0
Where
192.168.x.xis the local IP of your computer (useipconfigon Windows,ip aon Linux).FAQ
Q: Does it work without a NAS, just on a local folder?
Yes. Replace the volume in
docker-compose.yml:Q: How to add a VPN so that the provider cannot see the torrents?
Add the gluetun service and switch Transmission to its network:
Q: How to update to new versions of the images?
Q: Can I access it externally, not just within the local network?
Yes, the easiest way is through Tailscale or Cloudflare Tunnel—without port forwarding on the router.
The stack is compact—three containers, one
docker-compose.yml, and one.envfile with passwords. It can be set up from scratch in five minutes, with files residing on the NAS, ensuring they remain intact during OS reinstalls.If you have a router with a USB drive at home and are still watching movies through a file explorer, give this setup a try. The difference is palpable.
Repository: https://github.com/vervs3/mediabox. I would appreciate a star ⭐ and welcome any questions in the comments—especially if you encounter other hurdles with your hardware.