Back to Home

Home Media Server on Docker: Transmission + Jellyfin + Telegram

Guide to Creating a Home Media Server Based on Docker with Integration of Transmission, Jellyfin, and Telegram Bot. NAS Support, Automatic Downloads, and Notifications.

Build Your Own Netflix in an Evening: Docker, Transmission, Jellyfin, and Telegram Bot
Advertisement 728x90

# Creating a Home Media Server: Transmission, Jellyfin, and Telegram Bot in Docker

In just one evening, you can assemble a full-fledged home media server with handy Telegram control. Ditch Transmission's outdated interface for the modern Flood UI, skip manual file copying with automatic indexing in Jellyfin, and replace constant monitoring with download completion notifications straight to your messenger. It all runs on top of network-attached storage (NAS) and launches with a single Docker command.

Docker-Based Solution Architecture

At the heart of the system are three containers sharing a common file system via a CIFS volume. Transmission handles torrent downloads, Jellyfin indexes media files and provides a viewing interface, while the Telegram bot acts as the user interface for control and notifications. The key feature is using a Docker volume with the CIFS driver, allowing direct mounting of the network share inside containers without tying to the host's local file system.

A common newbie mistake is trying to use bind mounts for network drives on Windows. Due to WSL2 quirks, such paths aren't visible inside containers. The fix is explicitly specifying the volume type as cifs with connection parameters:

Google AdInline article slot
volumes:
  downloads:
    driver: local
    driver_opts:
      type: cifs
      device: ${SMB_HOST}
      o: "username=${SMB_USER},password=${SMB_PASSWORD},vers=3.0,uid=1000,gid=1000,file_mode=0777,dir_mode=0777"

Both services—Transmission and Jellyfin—use this volume but with different mount points inside the containers, ensuring consistent paths and no conflicts.

Setting Up the Telegram Bot and Automation

The bot is built in Python using the python-telegram-bot library and interacts with Transmission via its RPC API. It runs in shared network stack mode with the Transmission container (network_mode: "service:transmission"), eliminating extra port forwarding and minimizing latency.

Key bot features:

Google AdInline article slot
  • Adding torrents by sending .torrent files or magnet links
  • Automatic completion notifications with a Unicode progress bar
  • Managing active downloads via inline message buttons
  • Notification filtering to prevent duplicates on restart

The progress bar is implemented with a simple function:

def progress_bar(pct, width=12):
    filled = round(pct * width)
    return "█" * filled + "░" * (width - filled)

To avoid spam on bot restarts, it tracks completed torrents by ID. On startup, the script initializes a completed_ids set, sending notifications only for newly finished downloads.

Fine-Tuning Transmission and Bypassing Image Limitations

The official linuxserver/transmission image has a quirk: on each launch, it overwrites parts of settings.json, ignoring environment variables. This affects settings like watch-dir, download-dir, and incomplete-dir. The solution is the custom-cont-init.d mechanism, which runs custom scripts after initialization but before starting the daemon.

Google AdInline article slot

Example script 01-fix-settings.sh:

#!/bin/sh
mkdir -p /downloads/Downloads
mkdir -p /downloads/.incomplete
mkdir -p /downloads/watch

SETTINGS=/config/settings.json
if [ -f "$SETTINGS" ]; then
    sed -i 's|"watch-dir": ".*"|"watch-dir": "/downloads/watch"|' $SETTINGS
    sed -i 's|"watch-dir-enabled": false|"watch-dir-enabled": true|' $SETTINGS
    sed -i 's|"download-dir": ".*"|"download-dir": "/downloads/Downloads"|' $SETTINGS
    sed -i 's|"incomplete-dir": ".*"|"incomplete-dir": "/downloads/.incomplete"|' $SETTINGS
    sed -i 's|"incomplete-dir-enabled": false|"incomplete-dir-enabled": true|' $SETTINGS
fi

Another issue is installing Flood UI. Newer image versions remove third-party interfaces. You need to manually download the archive from GitHub and unpack it into config/flood-ui/. The repo includes a setup-flood.sh script for automation.

First Jellyfin Launch and TV Integration

After starting the containers, open the Jellyfin web interface at http://localhost:8096. Initial setup involves creating a user and adding a media library. Point to /media/Downloads as the source—that's how the shared folder is mounted in the Jellyfin container.

Jellyfin automatically:

  • Scans folder contents
  • Identifies content types (movies, TV shows)
  • Fetches metadata and posters from TMDB
  • Builds intuitive navigation by genres, years, actors

For TV viewing, install the official Jellyfin app (supports Android TV, Apple TV, Fire TV, Roku). On first launch, enter your host's local IP and port 8096. Log in with the user you created earlier.

Common Questions and Functionality Extensions

No NAS? Swap the CIFS volume for a local folder with a bind option:

volumes:
  downloads:
    driver: local
    driver_opts:
      type: none
      device: /absolute/path/to/folder
      o: bind

Want VPN? Use the gluetun container and shift Transmission to its network namespace:

transmission:
  network_mode: "service:gluetun"
  depends_on:
    - gluetun

How to update? Run:

docker compose pull && docker compose up -d

Remote access? Use Tailscale or Cloudflare Tunnel—safer than router port forwarding.

Key Points

  • CIFS volumes over bind mounts are crucial for network drives in Docker Desktop on Windows
  • The custom-cont-init.d mechanism bypasses official Transmission image restrictions
  • Shared network stack between bot and Transmission simplifies the setup and boosts reliability
  • Jellyfin auto-enriches media with metadata—no manual tweaks needed
  • All data lives on external NAS, making the system OS-reinstall-proof

— Editorial Team

Advertisement 728x90

Read Next