Setting up a Home Media Server

And other voyages across the high seas

Harry Keightley - 2023-08-08


Last month, my relationship with netflix officially ended.

netflix-booting-me-off

More precisely, my family’s relationship with netflix (off which I was comfortably mooching) changed, ending 8 years of a great thing. Towards the end, I’ll admit I was becoming less satisfied with the extents of netflix’s library, and a sort of fatigue was setting in where I’d have to ping-pong between different streaming platforms to find the titles I wanted. If this sounds like a retrospective “Fine, I never liked you anyway”, I’ll admit, you’re right; but nonetheless, this was the final kick I needed to start looking into some more sustainable alternatives to streaming services as a whole.

In this article, I’ll go through the steps I took in setting up my home media server, and give solutions to the roadblocks I faced.

Table of Contents

  1. Overview
    1. Disclaimer
    2. Value Proposition
    3. Requirements
    4. The tech stack
  2. Prepping the Hardware
    1. Dell Optiplex BIOS witchcraft
    2. Preparing and Installing Linux
    3. Router Configuration
  3. Software
    1. Why Docker?
    2. Installing Docker
    3. The Best Docker Setup
    4. My docker compose file
    5. Running the docker services
    6. Service Configuration
    7. Jellyfin Clients, and building for Samsung TV OS
  4. Next Steps

1. Overview

1.1 Disclaimer

Piracy is a crime. Hilariously, the producers of that ad didn’t have the rights to the music they used.

Throughout this article, the terms media and media library will henceforth refer to the legal downloading and storage of linux .iso files. This is to make it absolutely clear, that media does not refer to movies, tv series, and the like.

1.2 Value Proposition

trade offer You receive:

You offer:

1.3 Requirements

For this guide, basic competency with *nix systems and the commandline is assumed. It is likely you will encounter your own unique set of roadblocks during installation, and you must be willing to fix these on your own.

1.4 Tech Stack

Among alternatives, the main idealogical factor here was choosing open source software. You could, for example, skip the linux installation and use windows 10 for your operating system. In this specific example I’d argue the effect would be like sucking soup through a paper straw.

I should note that Plex is touted as a common alternative to Jellyfin. Plex has prebuilt clients for a large number of devices, at the downside that you have to sign up for an account with them, and it’s not OSS. That was a no from me.

2. Hardware

In this section, we’ll reformat the optiplex, install linux on it, and then assign it a static local ip adress from the router.

2.1 Preparing the Optiplex

It’s BIOS time baby. Boot that machine up and spam F11 and F12, because I can never remember which one’s which. We want to get to the BIOS options menu, so we can:

A key resource for me here was this video by Phillip Yip, showing from scratch how he installs Ubuntu on the same machine. He covers points, a and d above. The other steps can be achieved from the Power Management tab in the BIOS settings.

If you’ve followed these steps so far, you’ll likely be seeing this gorgeous interface:

Wiping the optiplex

Do not, I repeat, do not believe the lies of the blue progress bar. I intently watched it fill up, only for it to start over again. If you don’t have an NVME drive, like me, this step can take a couple hours; so in the meantime, let’s get Fedora.

2.2 Preparing and installing linux.

The most important thing I can do at this stage is warn you not to follow in my footsteps.

The Dell Optiplex will only see/boot from USB 3.0 Flash Drives!!

I initially misread this and thought it meant to make sure to plug the usb into a USB 3.0 slot on the back of the machine. Consequently, I wasted ~4 hours scratching my head and having heartfelt conversations with the machine, begging it to work.

Now that’s out of the way, download and flash Fedora to your usb. The second time, I used Fedora Media Writer to do this from my mac, and would strongly recommend this. You could also use dd, or rufus, if these are available to you.

After you’re done, here are some ideas for fun tasks to occupy you instead of watching that blue progress bar:

Finally, insert the USB, reboot the computer, and boot from the USB.

Go through the comfy graphical installation process for Fedora. The only thing of note here was that I deleted all existing partitions and let the installer automatically do the allocations, but you do you. When done, remove that usb and reboot from your new OS.

2.3 Router Configuration

Here the most important step is to give your new machine a static ip address, or else you might one day wakeup with none of your services pointing to the right places. First on linux, bring up a terminal and type:

ifconfig -a

From the results, find your local ip.

The next steps change drastically based on your router, but:

  1. Open up your router settings in a browser.
  2. Find your way to the DHCP settings.
  3. Find the MAC address associated with your machine’s local ip address.
  4. Make that binding static.

As an additional step, if you can, I’d set your upstream DNS to not point to your ISP’s servers. I just tried to do this with a Telstra router, and I was absolutely shocked that they lock this option down. If this is the case with you as well, I’d look up how to just change the DNS server for the fedora machine instead of the entire network.

Some common DNS options here:

  1. Google DNS - 8.8.8.8, 8.8.4.4
  2. CloudFlare DNS - 1.1.1.1, 1.0.0.1

3. Software

Now that we have our machine up and running, things get a little more interesting. We’re going to set up a series of docker containers with docker-compose, which are going to communicate with eachother via the local network, download media from the web, and store and organise files on disk.

Similar to the rest of the guide, I won’t be providing step by step instructions, and will instead link material which explains how to do everything in much better detail than I can muster in a brief article.

3.1 Why docker?

You can absolutely install each of these programs manually instead of through containers, so why use docker?

I’d argue that the main points here are security and ease of use. Using containers, we can isolate each application from the rest of the computer, and eachother. We can give each application least responsibility by only allowing access to the directories that we choose them to. In effect, we drastically reduce the possibilities of what can go wrong in our system.

The other point is that docker makes the composing and maintenance of these applications incredibly easy. Want an entirely new application to interop with the rest? Just add a few lines to your docker-compose file and restart. A nice view of your processes? docker stats. I also want to configure only as much as I need for a given result, and docker allows me to do just that.

3.2 Installing Docker

The only links you need for this step are here and here.

By the end of the installation process, you should have:

3.3 The Best Docker setup.

Here I think you should spend a bit of time reading this amazing resource from wiki.servarr.com. This wiki has a tonne of useful configuration and troubleshooting guides for all of the services we’re about to set up, so I’d bookmark and come back to it after you’ve read that initial link. The other really helpful resource at this stage is trash guides.

Based off the listed guides, here is the setup I ended up using:

  1. Created a new user group (media) for all the applications.
  2. Created new users for each of the applications, and added them to the media group. (Added $USER as well)
  3. Created my root data folder at /data.
  4. Gave the data folder root:media ownership with chown.
  5. Changed folder permissions as per the guides with chmod.
  6. Inside the /data folder, used a structure very similar to this, but with an extra folder: /data/config for application configuration files.

It goes without saying, but I will say it anyway- the directory structure I used is necessary for my provided docker compose file to work. Don’t @ me if you try to use it with a different setup and it bites you in the ass.

3.4 My docker compose file.

To really see what I mean by how easy it was to use all these applications together- take a look at how small my docker compose is:

Click to expand my docker-compose.yaml.
version: "3.7"

services:
    Sonarr:
        container_name: Sonarr
        restart: unless-stopped
        image: cr.hotio.dev/hotio/sonarr
        ports:
            - "8989:8989"
        volumes:
            - /data/config/sonarr:/config
            - /data:/data
        environment:
            - PUID=1111 # Application user id setup in previous step
            - PGID=1002 # Media group id
            - UMASK=002
            - TZ=Australia/Brisbane # Obviously replace this.

    Radarr:
        container_name: radarr
        restart: unless-stopped
        image: cr.hotio.dev/hotio/radarr
        ports: 
            - "7878:7878"
        volumes:
            - /data/config/radarr:/config
            - /data:/data
        environment:
            - PUID=1112
            - PGID=1002
            - UMASK=002
            - TZ=Australia/Brisbane

    Prowlarr:
        container_name: prowlarr
        restart: unless-stopped
        image: cr.hotio.dev/hotio/prowlarr
        ports:
            - "9696:9696"
        volumes:
            - /data/config/prowlarr:/config
        environment:
            - PUID=1113
            - PGID=1002
            - UMASK=002
            - TZ=Australia/Brisbane

    Jellyfin:
        container_name: jellyfin
        restart: unless-stopped
        image: cr.hotio.dev/hotio/jellyfin
        ports:
            - "8096:8096"
        volumes:
            - /data/config/jellyfin:/config
            - /data/media:/data/media
        environment:
            - PUID=1114
            - PGID=1002
            - UMASK=002
            - TZ=Australia/Brisbane

    QBitTorrent:
        container_name: qbittorrent
        restart: unless-stopped
        image: cr.hotio.dev/hotio/qbittorrent
        ports: 
            - "8080:8080"
        volumes:
            - /data/config/qbittorrent:/config
            - /data/torrents:/data/torrents
        environment:
            - PUID=1115
            - PGID=1002
            - UMASK=002
            - TZ=Australia/Brisbane

3.5 Running the docker services

Ready for the magic? Navigate to wherever you made that docker-compose.yaml file, and enter:

docker compose up -d

running-docker-services It’s alive! Also, whenever your computer restarts, these services should as well.

3.6 Configuring the services

Here’s where we return to the servarr wiki and trash guides. We need to do some basic setup for each of the services we’ve just started.

To access any of these applications, go to the local ip address you found in step 2.4, suffixed with the application port, e.g. if your local ip was 192.168.0.1 and you wanted to access Prowlarr, you would open a web browser and go to 192.168.0.1:9696, since in the docker compose file we set 9696 to be Prowlarr’s port.

Each of the services (apart from jellyfin) has a corresponding set of pages for configuration on servarr or in trash-guides, that you should follow.

As for recommended order, I would go:

  1. qbittorrent: trash-guides link
  2. prowlarr: servarr quick-start
  3. sonarr: servarr quick-start
  4. radarr: servarr quick-start
  5. jellyfin: I couldn’t find links in servarr or trash-guides. It’s a pretty chill setup, so if needed, I’d go through with a youtube video like this, but ignoring plugins at this stage until you’re familiar with jellyfin.

At this stage, you should be completely ready to go! The servarr wiki has guides on how to use the individual services. Welcome aboard me-hearty!

3.7 Jellyfin clients

To actually watch the media you’ve just setup all these processes for, you can either go to the jellyfin server ip on any device in your local network, or find a client for the specific device you’re on. I find for my computers I just visit the ip addresss, but for TV, I wanted a native client, so I had some more hacking to do.

Enter, the godsend. This man really provided a single docker image which, given the ip of your (samsung) tv, does the entire build and installation process! Amazing.

I had one significant roadblock here, which was that I needed to increase the number of files allowed to spawn from my docker container, by running a slightly different version of the supplied command:

docker run --rm --ulimit nofile=<bigint>:<bigint> georift/install-jellyfin-tizen <samsung tv ip>

Setting bigint to bigger and bigger values until it worked. I can’t remember what I used at this time. Good luck.

4. Next Steps

Enjoy yourself a little bit. You deserve it. To improve this setup, the two places I’d suggest are:

  1. Add a pihole into the mix. In addition to blocking ads for your whole network, you can assign local hostnames to ip addresses. For example, I have my server setup to be at home.server on the local network.
  2. Along the same lines, a small quality of life fix could be to create a reverse proxy, which you could use to point addresses like jellyfin.server to your jellyfin service, instead of having to remember ports and ip addresses.
  3. If you feel brave, look into seting up a VPN à la wireguard, which will give you access to all your media from outside the local network.

Above all, I hope you enjoy the fruits of your labour.