Transmission is a free, open-source BitTorrent client that is famously light on resources. It has a simple web interface, supports magnet links, encryption and watch folders, and works happily as the download client for Sonarr, Radarr and Lidarr.
In this guide we will run Transmission in Docker using the linuxserver.io image, protect the web UI with a password, point it at a shared /data folder so your *arr apps can hardlink finished downloads, and get it ready to sit behind a reverse proxy.
β Before you start
- A Linux host with Docker and Docker Compose v2 installed (
docker compose versionshould work). - Enough space on one disk or pool for your downloads and your media library.
- Two free ports:
9091for the web UI and51413(TCP and UDP) for incoming peers. - Optional: a port forward on your router for
51413. Only forward the torrent port, never the web UI.
STEP 1: Find your user and group IDs
Transmission should run as a normal user, not as root. Log in to your Docker host over SSH and run:
idYou will see something like uid=1000(you) gid=1000(you). Make a note of the two numbers; they become PUID and PGID below.
Use the same
PUID and PGID for Sonarr, Radarr, Lidarr, qBittorrent and Transmission. They all read and write the same files, so they need to agree on who owns them.STEP 2: Create the folders
Every app in my media stack shares one data folder, with downloads and the finished library side by side. Create the app's own config folder and the shared layout (skip the second block if you already made it for Sonarr, Radarr or Lidarr):
mkdir -p /path/to/appdata/transmission/config /path/to/appdata/transmission/watch
mkdir -p /path/to/data/{torrents/{movies,tv,music},media/{movies,tv,music}}
sudo chown -R 1000:1000 /path/to/data
sudo chmod -R u=rwX,g=rwX,o=rX /path/to/dataSwap 1000:1000 for your own IDs from STEP 1. When you are done, the shared folder should look like this:
/path/to/data/
torrents/ # the download client saves here
movies/
tv/
music/
media/ # your finished library (Jellyfin reads this)
movies/
tv/
music/Shared data layout
Hardlinks and instant (atomic) moves only work inside a single file system, and Docker treats every volume you mount as a separate one. If downloads and the library are mounted separately (the classic
/downloads and /tv), Transmission has to copy each file and then delete the original: slow, and while a torrent is still seeding it uses twice the disk space. Mount the one /data folder in every container instead and the paths match everywhere, so an import becomes an instant hardlink: the file appears in your library and keeps seeding from the same blocks on disk. This is the layout the TRaSH Guides recommend./path/to/data must live on one disk or pool. Hardlinks cannot cross from one disk to another, so do not split torrents and media across drives.Finally, I put all my media apps on one shared Docker network so they can reach each other by name (for example, Sonarr reaching http://transmission:9091). If you followed the Jellyfin guide you already have it; otherwise create it once:
docker network create mediaSTEP 3: The compose file
Create /path/to/appdata/transmission/docker-compose.yml and paste in the following. Use the copy button in the top right corner of the block.
services:
transmission:
image: lscr.io/linuxserver/transmission:latest
container_name: transmission
hostname: transmission
restart: unless-stopped
environment:
- PUID=1000 # your user ID from STEP 1
- PGID=1000 # your group ID from STEP 1
- TZ=Europe/London # your time zone
- UMASK=002 # new files are group-writable
- USER=admin # web UI username
- PASS=change-me # web UI password (change it!)
- PEERPORT=51413 # must match the port mappings below
# - HOST_WHITELIST=transmission.example.com
volumes:
- /path/to/appdata/transmission/config:/config
- /path/to/data:/data # the same mount as Sonarr, Radarr and Lidarr
- /path/to/appdata/transmission/watch:/watch
ports:
- 9091:9091 # web UI and RPC
- 51413:51413 # incoming peers (TCP)
- 51413:51413/udp # incoming peers (UDP)
networks:
- media
networks:
media:
external: truedocker-compose.yml
STEP 4: The settings explained
| Setting | What it does |
|---|---|
PUID / PGID |
The user and group Transmission runs as. Downloads will belong to this user, so the *arr apps (running as the same user) can hardlink them. |
TZ |
Time zone, for the scheduler and logs. List of zones. |
USER / PASS |
The web UI login. Without them anyone on your network can control Transmission. Set them here rather than in settings.json, otherwise the container cannot stop Transmission cleanly. |
PEERPORT |
Fixes the peer port to 51413 instead of a random one, so it matches the port mapping and your router forward. |
HOST_WHITELIST |
Optional. A comma-separated list of hostnames allowed to reach the web UI, such as your reverse proxy domain. Leave it commented out and the check stays off. |
/config |
Settings (settings.json), torrent resume files and logs. This is the folder to back up. |
/data |
The shared folder from STEP 2. Transmission only writes to /data/torrents. |
/watch |
Drop a .torrent file in here and Transmission adds it automatically. |
STEP 5: Start Transmission
From the folder containing your compose file:
cd /path/to/appdata/transmission
docker compose up -d
docker compose logs -fWait until the logs settle down (press Ctrl+C to stop following them; the container keeps running), then check it is up:
docker ps --filter name=transmissionSTEP 6: First-run setup
Open http://192.168.1.20:9091 in your browser (use your Docker host's IP) and sign in with the USER and PASS from your compose file.
Out of the box the image saves to /downloads/complete and /downloads/incomplete, which we have not mounted. Fix that first: open the menu, choose Edit Preferences, and on the Torrents tab:
- Set Download to to
/data/torrents. - Either untick Use temporary folder, or set it to
/data/torrents/incomplete. It must stay inside/data. - Close the dialog; Transmission saves preferences as you change them.
Transmission has no categories, so tell each *arr app where to save instead. In Sonarr, Radarr and Lidarr, add Transmission under Settings β Download Clients with host transmission, port 9091, your username and password, and set Directory to /data/torrents/tv, /data/torrents/movies or /data/torrents/music respectively.
The linuxserver.io image can switch to an alternative web UI such as Flood for Transmission or Transmission Web Control with the
TRANSMISSION_WEB_HOME variable. See the image docs for the exact paths.STEP 7: Put Transmission behind a reverse proxy
Never forward port 9091 from your router. Put the web UI behind a reverse proxy with HTTPS, keep the USER/PASS login on, or keep it private and use a VPN such as WireGuard or Tailscale.
Option A: Traefik (Docker labels)
If Traefik runs on the same Docker host and is attached to the media network, add these labels to the transmission service. They assume your HTTPS entry point is called websecure and your certificate resolver is called cloudflare; change them to match your Traefik config.
labels:
- traefik.enable=true
- traefik.http.routers.transmission.rule=Host(`transmission.example.com`)
- traefik.http.routers.transmission.entrypoints=websecure
- traefik.http.routers.transmission.tls.certresolver=cloudflare
- traefik.http.services.transmission.loadbalancer.server.port=9091docker-compose.yml (extra lines)
Option B: Traefik (file provider)
If Traefik runs on a different machine, add a router and service to your dynamic config file instead:
http:
routers:
transmission:
rule: Host(`transmission.example.com`)
entryPoints:
- websecure
tls:
certResolver: cloudflare
service: transmission
services:
transmission:
loadBalancer:
servers:
- url: http://192.168.1.20:9091dynamic.yml
Option C: Nginx Proxy Manager
Add a Proxy Host for transmission.example.com with scheme http, forward hostname transmission (if NPM is on the media network) or 192.168.1.20, and forward port 9091. Request a certificate on the SSL tab and tick Force SSL.
If you turn on HOST_WHITELIST, include your proxy hostname in it (for example transmission.example.com), or Transmission will refuse requests that arrive with that Host header.
Updating Transmission
The latest tag always follows the newest stable release. To update:
cd /path/to/appdata/transmission
docker compose pull
docker compose up -d
docker image prune -flinuxserver.io also publishes version tags, for example
lscr.io/linuxserver/transmission:4.1.3-r0-ls363 (see the Tags list on the image page). Pin one and bump it deliberately after reading the release notes, and back up /config first.docker compose stop). Transmission rewrites the file when it shuts down, so changes made while it is running are lost.Troubleshooting
Downloads disappear when the container is recreated. Transmission is still saving to /downloads/... inside the container. Change the download and temporary folders to /data/torrents as in STEP 6.
Torrents show Permission denied. The container user cannot write to /data/torrents. Check docker exec -it transmission ls -ln /data/torrents shows your PUID/PGID and fix it with the commands from STEP 2.
The *arr apps copy instead of hardlinking. Both containers must mount /path/to/data:/data, and the download directory must be under /data/torrents.
421 Misdirected Request through the proxy. HOST_WHITELIST is set but does not include your proxy hostname. Add it, then run docker compose up -d.
port is already allocated when starting. Something else is using 9091 or 51413. Change the left-hand side of the web UI mapping (for example 9092:9091), or change PEERPORT and both sides of the peer mappings together.
That's it! A tiny, tidy torrent client that plays nicely with the rest of your stack. Happy (legal) torrenting! π§²