Downloads
Transmission logo

How to install Transmission with Docker

Transmission Docker install guide cover
Difficulty: BeginnerTime: 20 minutesTested with: Transmission 4.1 (linuxserver.io image)

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.

πŸ’‘
Note: This guide works with the current Transmission 4.1 release (4.1.3 at the time of writing) from the linuxserver.io image.

βœ… Before you start

  • A Linux host with Docker and Docker Compose v2 installed (docker compose version should work).
  • Enough space on one disk or pool for your downloads and your media library.
  • Two free ports: 9091 for the web UI and 51413 (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:

id

You will see something like uid=1000(you) gid=1000(you). Make a note of the two numbers; they become PUID and PGID below.

πŸ‘
Tip: One user for the whole stack
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/data

Swap 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

πŸ’‘
Note: Why one /data folder?
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.
⚠️
Warning: /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 media

STEP 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: true

docker-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 -f

Wait 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=transmission

STEP 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:

  1. Set Download to to /data/torrents.
  2. Either untick Use temporary folder, or set it to /data/torrents/incomplete. It must stay inside /data.
  3. 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.

πŸ‘
Tip: Prefer a fancier interface?
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=9091

docker-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:9091

dynamic.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 -f
πŸ‘
Tip: Prefer to update on your own schedule?
linuxserver.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.
⚠️
Warning: Editing settings.json by hand? Stop the container 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! 🧲