Sonarr is a free, open-source PVR for TV shows. Tell it which series you want and it watches your indexers for new episodes, sends them to your download client, then renames and files them neatly in your library. It can even upgrade episodes when a better quality turns up. There is no subscription, and everything stays on your own server.
In this guide we will run Sonarr in Docker using the linuxserver.io image, set up a shared /data folder so imports are instant hardlinks instead of slow copies, connect it to a download client, 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). - A download client such as qBittorrent or Transmission, ideally set up with the same
/datalayout (both have their own Nerdy Guides). - At least one indexer. The easiest way to manage these is Prowlarr, which pushes indexers to every *arr app for you.
- Enough space on one disk or pool for your downloads and your TV shows library.
- Optional: a reverse proxy such as Traefik or Nginx Proxy Manager if you want to reach Sonarr from outside your LAN.
STEP 1: Find your user and group IDs
Sonarr 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 another *arr app or your download client):
mkdir -p /path/to/appdata/sonarr/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/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), Sonarr 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://qbittorrent:8080). 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/sonarr/docker-compose.yml and paste in the following. Use the copy button in the top right corner of the block.
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
hostname: sonarr
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
volumes:
- /path/to/appdata/sonarr/config:/config
- /path/to/data:/data # ONE mount for downloads and library
ports:
- 8989:8989 # web UI
networks:
- media
networks:
media:
external: truedocker-compose.yml
/tv and /downloads mounts from the image's example. They work, but they are exactly what breaks hardlinks. Everything lives under /data.STEP 4: The settings explained
| Setting | What it does |
|---|---|
image |
The linuxserver.io build of Sonarr. The latest tag follows stable releases. |
PUID / PGID |
The user and group Sonarr runs as inside the container. Files it creates in /config and /data belong to this user. |
TZ |
Time zone, so schedules, the calendar and logs use your local time. List of zones. |
UMASK |
Optional. 002 makes new files and folders group-writable, handy when several apps share one group. |
/config |
Sonarr's database, settings and logs. This is the folder to back up. |
/data |
The shared folder from STEP 2: downloads in /data/torrents and your library in /data/media, on one file system so hardlinks work. |
8989:8989 |
The web UI. Change the left-hand number if the port is already taken on your host. |
networks |
Joins the shared media network so Sonarr can reach your download client, Prowlarr and Jellyfin by container name. |
STEP 5: Start Sonarr
From the folder containing your compose file:
cd /path/to/appdata/sonarr
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=sonarrAfter a few seconds the status should read Up.
STEP 6: First-run setup
Open http://192.168.1.20:8989 in your browser, replacing the IP with your Docker host's address.
- Set up a login. Sonarr asks you to choose an authentication method straight away. Pick Forms (Login Page), leave Authentication Required on Enabled, and choose a username and a strong password such as
CHANGE_ME_STRONG_PASSWORD(please do change it). - Add your root folder. Go to Settings β Media Management, click Add Root Folder and choose
/data/media/tv. - Check hardlinks are on. On the same page, click Show Advanced and make sure Use Hardlinks instead of Copy is ticked (it is by default). Click Save Changes.
- Add your download client. Go to Settings β Download Clients, click + and pick qBittorrent (or Transmission). Set Host to
qbittorrent(the container name, thanks to the shared network) and Port to8080(Transmission:transmissionand9091), enter the client's username and password, and change Category fromtv-sonarrtotvso downloads land in/data/torrents/tv. Click Test, then Save. - Add indexers. Either add them under Settings β Indexers, or (much easier with several *arr apps) add them once in Prowlarr and let it sync them to Sonarr.
- Grab your API key. Settings β General shows the API key that Prowlarr, Jellyseerr, JellyGlance and Homarr widgets use to talk to Sonarr.
That's the core setup done. Add your first few series from the search bar and watch them appear under Activity.
Because Sonarr and your download client both see the same
/data path, you do not need any Remote Path Mappings. If a guide tells you to add one, your paths do not match; fix the mounts instead.STEP 7: Put Sonarr behind a reverse proxy
Never forward port 8989 straight from your router. Put Sonarr behind a reverse proxy with HTTPS, keep the login from STEP 6 enabled, 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 sonarr 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.sonarr.rule=Host(`sonarr.example.com`)
- traefik.http.routers.sonarr.entrypoints=websecure
- traefik.http.routers.sonarr.tls.certresolver=cloudflare
- traefik.http.services.sonarr.loadbalancer.server.port=8989docker-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:
sonarr:
rule: Host(`sonarr.example.com`)
entryPoints:
- websecure
tls:
certResolver: cloudflare
service: sonarr
services:
sonarr:
loadBalancer:
servers:
- url: http://192.168.1.20:8989dynamic.yml
Option C: Nginx Proxy Manager
Add a Proxy Host for sonarr.example.com with scheme http, forward hostname sonarr (if NPM is on the media network) or 192.168.1.20, and forward port 8989. Request a certificate on the SSL tab and tick Force SSL.
Sonarr needs no special headers on its own subdomain. If you would rather serve it on a path such as https://example.com/sonarr, set Settings β General β URL Base to /sonarr first and restart the container.
Updating Sonarr
The latest tag always follows the newest stable release. To update:
cd /path/to/appdata/sonarr
docker compose pull
docker compose up -d
docker image prune -fThe Sonarr team are clear that Docker users should update the container image, not use the built-in updater. linuxserver.io also publishes version tags (see the Tags list on the image page) if you would rather pin one and bump it after reading the release notes. Back up
/path/to/appdata/sonarr/config first.Troubleshooting
Imports are slow or files are copied, not hardlinked. The download client and Sonarr are not seeing the same file system. Check both containers mount /path/to/data:/data (not separate download and library folders), and that torrents and media are on the same disk. ls -li on a file in both places should show the same inode number.
"Path does not exist" or "access denied" on the root folder. This is almost always permissions. Run docker exec -it sonarr ls -ln /data/media: the owner should match your PUID/PGID. Fix it on the host with the chown/chmod commands from STEP 2.
The download client test fails. Make sure both containers are on the media network (docker network inspect media) and that you used the container name, not localhost. Inside a container, localhost is the container itself.
Downloads finish but never import. Look at Activity β Queue and hover over the warning icon. A category mismatch (the client saving outside /data/torrents/tv) or a file still being written are the usual causes.
port is already allocated when starting. Something else is using 8989. Change the left-hand side of the port mapping, for example 8990:8989.
That's it! Sonarr is now quietly looking after your TV shows for you. Put the kettle on and let it do the hunting. πΊ