Downloads
qBittorrent logo

How to install qBittorrent with Docker

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

qBittorrent is a free, open-source BitTorrent client with a clean web interface. It is fast, has no adverts, and has a proper API, which makes it the most popular download client for Sonarr, Radarr and Lidarr.

In this guide we will run qBittorrent in Docker using the linuxserver.io image, point it at a shared /data folder so your *arr apps can hardlink finished downloads instead of copying them, and get it ready to sit behind a reverse proxy.

πŸ’‘
Note: This guide works with the current qBittorrent 5.2 release (5.2.4 with libtorrent 2.0 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: 8080 for the web UI and 6881 (TCP and UDP) for incoming peers.
  • Optional: a port forward on your router for 6881, so other peers can connect to you. Only forward the torrent port, never the web UI.

STEP 1: Find your user and group IDs

qBittorrent 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/qbittorrent/config

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), qBittorrent 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://qbittorrent:8080). 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/qbittorrent/docker-compose.yml and paste in the following. Use the copy button in the top right corner of the block.

services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    hostname: qbittorrent
    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
      - WEBUI_PORT=8080      # must match the port mapping below
      - TORRENTING_PORT=6881 # must match the port mappings below
    volumes:
      - /path/to/appdata/qbittorrent/config:/config
      - /path/to/data:/data  # the same mount as Sonarr, Radarr and Lidarr
    ports:
      - 8080:8080            # web UI
      - 6881:6881            # incoming peers (TCP)
      - 6881:6881/udp        # incoming peers (UDP)
    stop_grace_period: 30s   # time to save resume data on shutdown
    networks:
      - media

networks:
  media:
    external: true

docker-compose.yml

STEP 4: The settings explained

Setting What it does
PUID / PGID The user and group qBittorrent runs as. Downloads in /data/torrents 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.
UMASK Optional. 002 makes new files group-writable.
WEBUI_PORT The port the web UI listens on inside the container. Because of qBittorrent's CSRF protection you cannot just remap the left-hand side: to use 8123, set WEBUI_PORT=8123 and map 8123:8123.
TORRENTING_PORT The port for incoming peer connections. As with the web UI, change both sides of the mapping and this variable together.
/config Settings, torrent resume data and logs. This is the folder to back up.
/data The shared folder from STEP 2. qBittorrent only writes to /data/torrents; the *arr apps hardlink from there into /data/media.
stop_grace_period Gives qBittorrent time to shut down cleanly. Raise it if you have hundreds of torrents.

STEP 5: Start qBittorrent

From the folder containing your compose file:

cd /path/to/appdata/qbittorrent
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=qbittorrent

qBittorrent prints a temporary password for the admin user in the log on first start. Find it with:

docker logs qbittorrent 2>&1 | grep -i password

STEP 6: First-run setup

Open http://192.168.1.20:8080 in your browser (use your Docker host's IP) and sign in as admin with the temporary password. Then click the cog icon to open Options:

  1. Web UI β†’ Authentication. Set your own username and a strong password, such as CHANGE_ME_STRONG_PASSWORD (please do change it). If you skip this, qBittorrent generates a new temporary password every time the container starts.
  2. Downloads β†’ Saving Management. Set Default Torrent Management Mode to Automatic and Default Save Path to /data/torrents. In automatic mode each category saves into its own subfolder, so the tv category lands in /data/torrents/tv.
  3. Connection. Check Port used for incoming connections shows 6881, and untick Use UPnP / NAT-PMP port forwarding; forward the port on your router yourself if you want to.
  4. Click Save at the bottom.

Now create the three categories: right-click Categories in the left sidebar, choose Add category, and add movies, tv and music (leave the save path empty and it uses the category name). Sonarr, Radarr and Lidarr can also create them for you the first time they send a download.

Finally, in each *arr app add qBittorrent under Settings β†’ Download Clients with host qbittorrent, port 8080, your new username and password, and the matching category (tv, movies or music).

πŸ‘
Tip: Keep seeding without wasting space
Because the *arr apps hardlink finished downloads into /data/media, you can keep seeding from /data/torrents for as long as you like and it costs no extra space. Set your ratio and seeding-time limits under Options β†’ BitTorrent β†’ Seeding Limits.

STEP 7: Put qBittorrent behind a reverse proxy

Never forward port 8080 from your router to qBittorrent. Put the web UI behind a reverse proxy with HTTPS, 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 qbittorrent 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.qbittorrent.rule=Host(`qbittorrent.example.com`)
      - traefik.http.routers.qbittorrent.entrypoints=websecure
      - traefik.http.routers.qbittorrent.tls.certresolver=cloudflare
      - traefik.http.services.qbittorrent.loadbalancer.server.port=8080

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:
    qbittorrent:
      rule: Host(`qbittorrent.example.com`)
      entryPoints:
        - websecure
      tls:
        certResolver: cloudflare
      service: qbittorrent
  services:
    qbittorrent:
      loadBalancer:
        servers:
          - url: http://192.168.1.20:8080

dynamic.yml

Option C: Nginx Proxy Manager

Add a Proxy Host for qbittorrent.example.com with scheme http, forward hostname qbittorrent (if NPM is on the media network) or 192.168.1.20, and forward port 8080. Request a certificate on the SSL tab and tick Force SSL.

Then, in qBittorrent, open Options β†’ Web UI, tick Enable reverse proxy support and add your proxy's IP address to Trusted proxies list, so the login ban and logs see real client addresses. If the page loads but you get Unauthorized through the proxy, untick Enable Host header validation or add your domain to its server domains list.

Updating qBittorrent

The latest tag always follows the newest stable release. To update:

cd /path/to/appdata/qbittorrent
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/qbittorrent:5.2.4_v2.0.15-ls479 (see the Tags list on the image page). There is also a libtorrentv1 tag if you need the older libtorrent. Pin one and bump it deliberately after reading the release notes, and back up /config first.

Troubleshooting

I cannot log in and the password keeps changing. You have not set a permanent password yet. Grab the latest temporary one from docker logs qbittorrent and set your own under Options β†’ Web UI.

The web UI shows Unauthorized after changing the port. You changed only one side. Set WEBUI_PORT and both sides of the port mapping to the same number, then run docker compose up -d.

Torrents stall on Errored or Permission denied. The container user cannot write to /data/torrents. Check docker exec -it qbittorrent ls -ln /data/torrents shows your PUID/PGID and fix it with the commands from STEP 2.

Sonarr or Radarr copies instead of hardlinking. qBittorrent is saving somewhere outside /data, or the two containers mount different host folders. Both must mount /path/to/data:/data, and the save path must be /data/torrents.

port is already allocated when starting. Something else is using 8080 (it is a popular port) or 6881. Change both sides of the mapping and the matching variable, for example 8081:8081 with WEBUI_PORT=8081.

That's it! Your downloads now land exactly where your *arr apps expect them. Happy (legal) torrenting! 🧲