Skip to content

About

A full jellyfin server setup designed for ease with docker compose and full setup instructions

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Self-Hosted Jellyfin Media Server — Setup Guide

Stack: Jellyfin, Sonarr, Radarr, Prowlarr, Seerr, qBittorrent.

Request management is handled by Seerr — the unified successor project that merges Jellyseerr and Overseerr into one actively maintained app supporting Jellyfin, Plex, and Emby.

All images are LinuxServer.io builds, except Seerr which is the project's own official image — both are multi-arch and run fine on your Oracle ARM VM. One difference worth knowing: Seerr doesn't use the PUID/PGID pattern the LinuxServer images do — it always runs as its built-in node user (UID 1000), so its config folder needs a manual chown (covered in the folder structure step below).

Note on privacy: this version has no VPN in front of qBittorrent, so its torrent traffic goes out directly on your VM's own connection/IP. If you later pick up a WireGuard-capable VPN subscription, it's a small change to add a wireguard container back and route qBittorrent's traffic through it — see the note at the end of this guide.


1. Architecture at a glance

  • qbittorrent / jellyfin / sonarr / radarr / prowlarr / seerr each run on two networks: an internal one for talking to each other, and a shared network with your existing Nginx Proxy Manager (NPM) instance so it can reverse-proxy them without you opening ports beyond what's needed.

2. Prerequisites

  • Docker + the Docker Compose plugin installed on the VM.
  • Your existing NPM instance's Docker network name (see step 6).
  • A storage mount with enough room for your media + downloads, ideally a block volume attached to the VM rather than the boot disk.

3. Folder structure

Keep media and downloads under the same root so Sonarr/Radarr can hardlink files during import instead of copying them:

/mnt/data/                     # <- DATA_ROOT in .env
├── media/
│   ├── movies/
│   └── tv/
└── torrents/
    ├── movies/
    └── tv/

Create it:

sudo mkdir -p /mnt/data/media/movies /mnt/data/media/tv \
              /mnt/data/torrents/movies /mnt/data/torrents/tv
sudo chown -R $USER:$USER /mnt/data

Then, alongside your docker-compose.yml, create per-service config folders:

mkdir -p config/{qbittorrent,jellyfin,sonarr,radarr,prowlarr,seerr}

Seerr's container always runs as UID 1000 internally (it ignores PUID/PGID), so its config folder specifically needs to be owned by 1000:1000 regardless of what your own user's ID is:

sudo chown -R 1000:1000 config/seerr

4. Environment file

cp .env.example .env

Fill in PUID/PGID (run id $USER), TZ, DATA_ROOT, and NPM_NETWORK_NAME (covered in step 6).


5. Find your existing NPM network

docker network ls
docker inspect <your-npm-container-name> --format '{{json .NetworkSettings.Networks}}'

Put whatever network name that prints into NPM_NETWORK_NAME in .env. If NPM is running as a standalone container (not compose-managed) on the default bridge, it's simpler to create a dedicated network and reconnect NPM to it:

docker network create npm_proxy_network
docker network connect npm_proxy_network <your-npm-container-name>

6. Deploy

docker compose up -d
docker compose logs -f

Check qBittorrent's logs for its auto-generated first-run WebUI password:

docker compose logs qbittorrent | grep -i password

(You'll change this immediately in the WebUI anyway.)

If you're managing this through Coolify instead of the CLI, add it as a "Docker Compose" resource pointing at this file — Coolify will handle up/down for you, but you still need the folders and .env created on the host first.


7. Post-deployment configuration

Prowlarr (do this first)

  1. Open Prowlarr → Indexers → add your trackers/indexers.
  2. Settings → Apps → add Sonarr and Radarr using each app's internal URL (http://sonarr:8989, http://radarr:7878) and API key (found in each app's Settings → General). Prowlarr will then push indexers to both automatically.

qBittorrent

  1. Log in at http://<vm-ip>:8080, change the password immediately (Settings → WebUI).
  2. Settings → Downloads → set default save path to /data/torrents, and enable "Keep incomplete torrents in a separate folder" if you want.

Sonarr / Radarr

  1. Settings → Media Management → add root folder: /data/media/tv (Sonarr) or /data/media/movies (Radarr).
  2. Settings → Download Clients → add qBittorrent, using host qbittorrent, port 8080.
  3. Prowlarr will populate indexers automatically once connected above.

Jellyfin

  1. First-run wizard → add libraries pointing at /data/media/movies and /data/media/tv.
  2. Create your admin account and any additional user accounts.

Seerr

  1. First-run wizard → choose Jellyfin as your media server, then sign in using your Jellyfin server URL (http://jellyfin:8096) and admin credentials.
  2. Settings → Services → add Sonarr (http://sonarr:8989) and Radarr (http://radarr:7878) with their API keys and the same root folders you set above.

8. Exposing services through your existing NPM instance

Because every service here shares a Docker network with NPM, you don't need to publish ports to the public internet at all — NPM reaches them by container name over the internal Docker network. For each service, in NPM add a Proxy Host:

Service Forward Hostname Forward Port Suggested domain
Jellyfin jellyfin 8096 jellyfin.yourdomain.com
Seerr seerr 5055 requests.yourdomain.com
Sonarr sonarr 8989 sonarr.yourdomain.com
Radarr radarr 7878 radarr.yourdomain.com
Prowlarr prowlarr 9696 prowlarr.yourdomain.com
qBittorrent qbittorrent 8080 qbit.yourdomain.com

For each Proxy Host: enable SSL (request a Let's Encrypt cert, force SSL), and enable Websockets Support — Jellyfin in particular needs this for live TV/sync features.

Jellyfin advanced tab — add this to avoid buffering issues on larger streams:

client_max_body_size 20M;
proxy_buffering off;

Lock down the admin panels

Sonarr, Radarr, Prowlarr, and qBittorrent's WebUIs are management interfaces, not end-user apps — don't expose them to the open internet without an extra layer:

  • Easiest: in NPM, create an Access List (Settings → Access Lists) with HTTP Basic Auth or an IP allowlist, and attach it to those four Proxy Hosts.
  • Better: don't create public Proxy Hosts for them at all — reach them over a VPN/Tailscale connection to the VM instead, and only expose Jellyfin and Seerr publicly (they have their own user-facing auth and are designed to be internet-facing).

Either way, set strong unique passwords on all of them — Prowlarr and Sonarr/Radarr's APIs are a real target if left open with default creds.


9. Troubleshooting

  • Imports copy instead of hardlink: confirm /data/torrents and /data/media are both under the same DATA_ROOT mount — hardlinks don't work across different filesystems/mounts.
  • NPM shows 502: confirm the target container is on the same Docker network as NPM (docker network inspect <network> should list both).

Optional: adding a VPN back later

If you get a WireGuard-capable VPN subscription down the line, the change is small:

  1. Add a wireguard service back to docker-compose.yml (config, NET_ADMIN/SYS_MODULE caps, and your provider's wg0.conf in config/wireguard/wg_confs/).
  2. On qbittorrent, remove its own ports: and networks: and add network_mode: "service:wireguard" instead, so its traffic is forced through the tunnel.
  3. Move qBittorrent's port mappings (8080, 6881) onto the wireguard service instead.
  4. Update Sonarr/Radarr's download client host from qbittorrent to wireguard, and NPM's qBittorrent Proxy Host forward hostname the same way.

About

A full jellyfin server setup designed for ease with docker compose and full setup instructions

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors