Self-HostingDocker

How to Build a Self-Hosted Media Management Stack on Docker: A Beginner-Friendly Jellyfin, Sonarr, and Radarr Guide

Learn how to build a beginner-friendly self-hosted media management stack on Docker with Jellyfin, Sonarr, Radarr, Bazarr, shared paths, and safer setup habits.

AU

Author

David Okonkwo

FTC disclosure: This article contains affiliate links. If you purchase through these links, we may earn a commission at no additional cost to you.

Key Takeaways

  • A beginner-friendly media management stack does not need to start huge. Jellyfin, Sonarr, Radarr, and a clean folder layout get you most of the value.
  • Shared paths matter more than people expect. If your containers do not see the same folders, imports break and storage gets messy fast.
  • Docker Compose makes the stack repeatable, easier to back up, and much less stressful to rebuild later.
  • Security should come before remote access. Keep admin panels local first, then add HTTPS and authentication once the basics work.
  • The best first goal is not “full automation.” It is “a stack you understand well enough to maintain.”

If you have ever looked at a self-hosted media stack and felt like everyone else got handed a secret dictionary first, you are not alone. Most guides throw a dozen app names at you, paste a giant Compose file, and somehow expect that to feel simple.

This guide takes the opposite approach.

We are going to build a beginner-friendly self-hosted media management stack on Docker using Jellyfin, Sonarr, Radarr, Bazarr, and Jellyseerr. I will explain what each tool does, why it matters, and how the pieces fit together before we touch the setup. We will also keep the stack grounded in a legal, privacy-first use case: managing media you already own, home videos, public-domain content, and content you are authorized to download and stream.

Think of this stack like a small library with helpful staff.

  • Jellyfin is the front desk and media catalog.
  • Sonarr and Radarr are the librarians keeping TV and movie sections organized.
  • Bazarr handles subtitles.
  • Jellyseerr lets household members request something without touching your admin panels.

Once you see the stack that way, it stops feeling like a pile of random containers and starts feeling like a system with jobs.

Why this matters before the setup

A lot of people start self-hosting media because they are tired of scattered files, changing streaming catalogs, and depending on services they do not control. That frustration is real, but the deeper reason this matters is control.

When you self-host your media stack, you decide:

  • where your files live
  • how your library is organized
  • who can access it
  • how much metadata, subtitles, and automation you want
  • when to update and when not to

That control is great, but it comes with one tradeoff: you become the person responsible for structure. If the folder layout is sloppy, if permissions are inconsistent, or if you expose the wrong service too early, the stack fights you.

So before we focus on convenience, we are going to build with clear paths, consistent permissions, and a minimum-viable design that you can actually troubleshoot.

What each app does

Here is the beginner version, in plain English.

Jellyfin

Jellyfin is your media server. It scans folders, pulls in metadata, and gives you a clean interface for streaming across your devices.

Sonarr

Sonarr manages TV libraries. It tracks show structure, seasons, and episode naming.

Radarr

Radarr does the same job for movies.

Bazarr

Bazarr handles subtitles and keeps them linked to your media library.

Jellyseerr

Jellyseerr gives you a request portal, which is much nicer than giving family members direct access to Sonarr and Radarr.

Optional services

You will see people add tools like Prowlarr, Tautulli, Tdarr, reverse proxies, SSO, and GPU-optimized transcoding stacks. Those can be useful, but they are not day-one requirements for a beginner build.

What you will need

For a simple starter setup, you need:

  1. A Linux host with Docker and Docker Compose installed
  2. At least 8 GB RAM if you want the stack to feel comfortable
  3. Enough storage for config files and your media library
  4. A user account with permission to access your media directories
  5. Local network access to the server while you do the first setup

A mini PC is plenty for many starter homelabs. If you are shopping for gear, these are the three products I would look at first:

The folder structure that saves you from future pain

This is the part many guides rush through, and it is the part that causes the most frustration later.

Why this matters: Sonarr, Radarr, and any download client all need to agree on where files live. If one container sees /downloads and another sees a completely different mount that only looks similar from the host side, imports become copy operations, hardlinks fail, and storage use balloons.

The easiest mental model is this: every app that touches media should be looking at the same filing cabinet, not its own private copy of the cabinet.

We will use this host layout:

mkdir -p ~/media-stack/{jellyfin,sonarr,radarr,bazarr,jellyseerr,qbittorrent}/config
mkdir -p /srv/media/{movies,tv,downloads}

Then make sure your media user owns those directories:

sudo chown -R $USER:$USER ~/media-stack
sudo chown -R $USER:$USER /srv/media

If you prefer to run the containers with a specific UID and GID, collect them now:

id

You will see output like this:

uid=1000(akash) gid=1000(akash) groups=1000(akash),27(sudo)

Write down your uid and gid. We will use them in the Compose file.

Step 1: Create the Docker Compose file

Why this matters: Docker Compose gives you a single source of truth. If the host dies, if you migrate to a new machine, or if you need to rebuild after a bad update, you have one file describing the stack.

Create ~/media-stack/docker-compose.yml:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
    ports:
      - "8096:8096"
    volumes:
      - ./jellyfin/config:/config
      - /srv/media:/media
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
    ports:
      - "8989:8989"
    volumes:
      - ./sonarr/config:/config
      - /srv/media/tv:/tv
      - /srv/media/downloads:/downloads
    restart: unless-stopped

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
    ports:
      - "7878:7878"
    volumes:
      - ./radarr/config:/config
      - /srv/media/movies:/movies
      - /srv/media/downloads:/downloads
    restart: unless-stopped

  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
    ports:
      - "6767:6767"
    volumes:
      - ./bazarr/config:/config
      - /srv/media/movies:/movies
      - /srv/media/tv:/tv
    restart: unless-stopped

  jellyseerr:
    image: fallenbagel/jellyseerr:latest
    container_name: jellyseerr
    environment:
      - LOG_LEVEL=info
      - TZ=UTC
    ports:
      - "5055:5055"
    volumes:
      - ./jellyseerr/config:/app/config
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
      - WEBUI_PORT=8080
    ports:
      - "8080:8080"
      - "6881:6881"
      - "6881:6881/udp"
    volumes:
      - ./qbittorrent/config:/config
      - /srv/media/downloads:/downloads
    restart: unless-stopped

If your id output showed a UID or GID other than 1000, replace those values before starting the stack.

What could go wrong

If you skip the UID and GID check, the apps might start but fail to write metadata, rename files, or import completed downloads. That is one of the most common beginner mistakes in self-hosted media stacks.

Step 2: Start the stack

Why this matters: launching everything at once lets you verify that your networking, ports, and volumes are at least valid before you start app-specific configuration.

From ~/media-stack run:

docker compose pull
docker compose up -d

Check status:

docker compose ps

You want to see containers in an Up state.

If one crashes immediately, inspect logs:

docker compose logs --tail=100 sonarr
docker compose logs --tail=100 radarr
docker compose logs --tail=100 jellyfin

Step 3: Open each service locally

Use your server IP or hostname on your LAN:

  • Jellyfin: http://your-server:8096
  • Sonarr: http://your-server:8989
  • Radarr: http://your-server:7878
  • Bazarr: http://your-server:6767
  • Jellyseerr: http://your-server:5055
  • qBittorrent: http://your-server:8080

Why this matters: local-first setup keeps your blast radius small. If something is misconfigured, you fix it before introducing HTTPS, reverse proxies, or internet exposure.

If remote access is part of your plan, come back to it after the stack works locally. Two HomelabAddiction guides worth reading next are How to Set Up Nginx Proxy Manager on Docker and How to Set Up HTTPS for Your Homelab.

Step 4: Set up Jellyfin first

Why this matters: Jellyfin is where you can see progress. It gives you a visible result quickly, which is good for motivation and good for troubleshooting.

In Jellyfin:

  1. Create your admin account.
  2. Add a Movies library pointed at /media/movies.
  3. Add a TV Shows library pointed at /media/tv.
  4. Let the first metadata scan finish.

If those folders are empty right now, that is fine. We are validating the structure.

Step 5: Configure Sonarr and Radarr

Why this matters: these apps are only helpful when they know where your media should end up. Think of them as organizers with rules. If the destination shelves are wrong, the whole system feels broken even when the containers themselves are healthy.

In Sonarr

  1. Go to Settings -> Media Management and enable rename options you actually want.
  2. Add a root folder: /tv
  3. Under Settings -> General, copy your API key somewhere safe.

In Radarr

  1. Go to Settings -> Media Management
  2. Add a root folder: /movies
  3. Under Settings -> General, copy the API key.

Optional download client wiring

If you use a compatible client for legal downloads, connect it under Settings -> Download Clients in Sonarr and Radarr.

For the qBittorrent container in this guide, the default path for completed downloads inside the app should be /downloads.

This is also where many people get stuck. If Sonarr or Radarr says a download finished but cannot import it, the usual cause is not magic. It is a path mismatch.

Step 6: Configure Bazarr and Jellyseerr

Why this matters: these are quality-of-life services, but they are the kind that make the stack feel polished instead of homemade.

Bazarr

  1. Connect Bazarr to Sonarr and Radarr using their API keys.
  2. Set your preferred subtitle languages.
  3. Run a test sync.

Jellyseerr

  1. Connect Jellyseerr to Jellyfin.
  2. Connect it to Sonarr and Radarr using their URLs and API keys.
  3. Set the default movie and TV destinations.

Now you have a cleaner request layer for your household instead of making everyone learn the admin apps.

Step 7: Add a few guardrails before you expose anything

Why this matters: media stacks are fun, and fun projects make people rush. The most common version of that rush is exposing services to the internet before passwords, HTTPS, or access boundaries are in place.

Before opening this stack beyond your LAN, do these three things:

  1. Use strong unique passwords for every admin account.
  2. Put the stack behind a reverse proxy with TLS.
  3. Add a real authentication layer for sensitive dashboards.

If you want a beginner-friendly auth path, read How to Set Up Authentik for Your Homelab. If you want to improve the Compose side of the stack itself, also read Docker Compose Best Practices in 2026.

Common mistakes

1. Using different paths inside different containers

This is the classic mistake. qBittorrent sees /downloads, but Sonarr sees /data/downloads, and now imports fail. Keep the path mapping simple and consistent.

2. Ignoring permissions

If your host user owns /srv/media but the containers run with a different UID and GID, file moves and metadata writes can fail quietly.

3. Starting with too many services

A twelve-container stack looks impressive, but it gives beginners twelve places to get confused. Start with the core, then add optional services after the basics work.

4. Exposing admin panels too early

Do not forward ports to the internet just because the UI loads locally. Get HTTPS and authentication right first.

5. Skipping backups for config data

Your media files matter, but your app databases and config directories matter too. Back up ~/media-stack along with your library metadata.

A simple upgrade path once the basics work

One reason people get overwhelmed by self-hosted media management is that they think they need the final form on day one. You do not.

A better path looks like this:

  1. Stage 1 - Jellyfin + folder layout
  2. Stage 2 - Add Sonarr and Radarr
  3. Stage 3 - Add Bazarr and Jellyseerr
  4. Stage 4 - Add reverse proxy, HTTPS, and SSO
  5. Stage 5 - Add monitoring, backups, and hardware acceleration tuning

That staged path matters because it gives you checkpoints. Each stage should feel stable before the next one begins.

Why shared paths are such a big deal

This deserves one more plain-English explanation because it is the single most useful concept in this whole guide.

Imagine your download client places a file into a basket labeled downloads. Sonarr or Radarr then needs to take that file and place it on the correct library shelf. If both apps are looking at the same room, that move is fast and predictable. If they are looking at different rooms that only happen to have similar labels, one app says, “I know the file is there,” while the other says, “I cannot see it.”

That is why you will hear experienced self-hosters talk so much about shared paths and hardlinks. It is not because they enjoy jargon. It is because folder mapping is the difference between a stack that feels smooth and a stack that feels cursed.

FAQ

Do I need all five apps to get started?

No. You can absolutely start with Jellyfin alone, then add Sonarr and Radarr once your library structure makes sense.

Can I run this on a mini PC?

Yes. A modest N100 or similar mini PC is enough for many beginner setups, especially if you are mostly doing direct play instead of heavy transcoding.

Should I choose Plex instead of Jellyfin?

If you want open-source software and more control, Jellyfin is the stronger fit for many homelabbers. If you want an easier commercial ecosystem, Plex still has appeal. Since HomelabAddiction already has a Jellyfin vs Plex vs Emby comparison, that is a good next read if you are still deciding.

What if I only want to manage files I already own?

That is completely valid. Jellyfin still makes sense on its own, and Sonarr or Radarr can remain optional until you need structured automation.

What is the safest way to add remote access later?

Use a reverse proxy with TLS, then protect admin routes with an auth layer such as Authentik. Do not start by opening raw ports to the internet.

What to learn next

Once this stack is running locally, the next skills worth adding are:

The nice thing about this stack is that it grows with you. You do not need to become the kind of person who memorizes every container flag by heart. You just need a clean layout, a repeatable Compose file, and enough patience to understand what each service is responsible for.

That is what makes a homelab sustainable.

Not the biggest stack. Not the flashiest screenshots. Just a system you can explain, maintain, and trust.