With streaming services becoming more fragmented, getting access to all your favorite content has become almost as inconvenient and expensive as it was in the days of cable packages.

Even if you happen to find your favorite content on a streaming platform, there’s no guarantee it will be around forever. A single discontinued licensing contract could mean losing access to countless pieces of media!

For these reasons, I have built up a fairly large music collection on my hard drive. I recently realized I needed a proper backup solution for my music and other important files.

I figured while I was at it, I would make my music collection streamable, because there’s lots of stuff Spotify and others don’t have. Then I figured since I was hosting music, I might as well start building a collection of movies and television for when I inevitably tire of paying for Netflix and Hulu.

Here’s a breakdown of the server I built:

PCPartPicker Part List

Type Item Price
CPU AMD Ryzen 3 2200G 3.5 GHz Quad-Core Processor $79.89 @ OutletPC
Motherboard Gigabyte B450M DS3H Micro ATX AM4 Motherboard $73.99 @ Amazon
Memory Corsair Vengeance LPX 8 GB (1 x 8 GB) DDR4-2400 Memory $39.99 @ Amazon
Storage Toshiba X300 8 TB 3.5" 7200RPM Internal Hard Drive $167.99 @ Amazon
Case Deepcool TESSERACT BF ATX Mid Tower Case $49.99 @ B&H
Power Supply Silverstone 300 W 80+ Bronze Certified SFX Power Supply $61.99 @ Amazon
Prices include shipping, taxes, rebates, and discounts
Total $473.84
Generated by PCPartPicker 2019-08-13 13:51 EDT-0400

I went cheap for this build, because all it needs to do is reliably store media files. I gave it a decent CPU and 8GB of memory so streaming this media to a couple clients should be no problem.

Software Stack

This diagram should give an overview of how our software talks to each other. Basically;

  • All programs are run in docker for portability and convenience
  • Sonarr watches usenet for TV shows I want
  • Couchpotato watches usenet for movies I want
  • SABnzbd downloads the NZBs found by Sonarr and Couchpotato
  • Jellyfin serves these media files with a pretty interface
  • Traefik generates SSL certs for the desired containers

Step 1: Setting up Docker

In this tutorial I will assume you are a docker novice. If you already have some familiarity with docker, you can probably skip to the next section.

Since I am running my machine with Ubuntu Server, I am going to install the necessary packages with the following command:

sudo apt install docker.io docker-compose

docker is a program that runs applications in containers. These containers share the kernelspace with the host machine, but isolate their own dependencies and runtime environments. This is great for application security and portability.

docker-compose is a functionality within docker that allows a series of containers to be preconfigured, so they can all be launched, maintained, and updated easily and conveniently.

I set up my “programs” folder with a docker-compose.yaml file, along with subfolders for every application to store configurations and metadata.

Step 2: Setting up Traefik

Traefik is a reverse proxy that integrates with Let’sEncrypt to dynamically provide SSL certificates to running applications. It will be able to securely direct HTTP requests to our server to the correct container.

If you aren’t planning on using these services outside your home network, it’s safe to skip this step. If you own a domain and wish to access your media anywhere, read on.

Traefik needs two files to work correctly. The Traefik configuration file, traefik.toml, and the configuration defining the container behaivior in docker-compose.yaml. Let’s take a look at that first.

version: '3'

services:      
   reverse-proxy:
      image: traefik:latest
      restart: always
      container_name: traefik
      ports: 
         - 80:80
         - 443:443
         - 8081:8081
      expose:
         - 8080
      networks:
         - traefik
      environment:
         - DO_AUTH_TOKEN=<or another DNS provider if you use one>
      volumes:
         - /var/run/docker.sock:/var/run/docker.sock
         - ./Traefik/traefik.toml:/traefik.toml
         - ./Traefik/acme.json:/acme.json
      labels: 
         - "traefik.backend=traefik"
         - "traefik.docker.network=traefik"
         - "traefik.enable=false"
         - "traefik.frontend.rule=Host:Host:monitor.mydomain.com"
         - "traefik.port=8080"
         - "traefik.frontend.headers.forceSTSHeader=true"
         - "traefik.frontend.headers.STSSeconds=315360000"
         - "traefik.frontend.headers.STSIncludeSubdomains=true"
         - "traefik.frontend.headers.STSPreload=true"

Some things to take away:

  • We need to bind ports 80 and 443 to receive HTTP/S traffic. These ports must be forwarded to your server from your router.
  • We mount our docker node to the traefik container so it can see our other containers
  • We mount traefik.toml and acme.json, the file used to store our SSL configurations.
  • traefik.enable is set to false because I don’t need to use it outside my network.

Then we have to define some more settings in traefik.toml

defaultEntryPoints = ["http", "https"]
logLevel = "DEBUG"

[entryPoints]
  [entryPoints.dashboard]
    address = ":8081"  # I changed from 8080 because SAB uses it
  [entryPoints.http]
    address = ":80"
      [entryPoints.http.redirect]
        entryPoint = "https"
  [entryPoints.https]
    address = ":443"
      [entryPoints.https.tls]

[api]
entrypoint="dashboard"

[acme]
   email = "me@mydomain.com"
   storage = "acme.json"   
   onHostRule = true
   entryPoint = "https"

   [acme.dnsChallenge]
      provider = "digitalocean"
      delayBeforeCheck = 0   

   [[acme.domains]]
      main = "*.mydomain.com"
     
[docker]
domain = "mydomain.com"
watch = true
network = "traefik"
  • The entrypoints section forces HTTPS and enables the dashboard on locahlhost:8081.
  • The acme section defines the settings for Let’sEncrypt, which may need to be tweaked for your setup.

The above file must be created:

touch acme.json && chmod 600 acme.json

To test out what we have so far, go to the root of your docker directory and run docker-compose up. Traefik was the hardest for me to configure, so read the output carefully for errors. If everything went OK, we should see our dashboard at localhost:8081.

Step 3: Docker Configurations

The other applications in this setup are mostly controlled through web interfaces. That means that after the container is up and running, you can change the settings via your web browser just how you like them.

I will go over the basic steps to get things running, but after that it’s up to you!

Here is the full docker compose file for my current setup

version: '3'
services:      
   reverse-proxy:
      image: traefik:latest
      restart: always
      container_name: traefik
      ports: 
         - 80:80
         - 443:443
         - 8081:8081
      expose:
         - 8080
      networks:
         - traefik
      environment:
         - DO_AUTH_TOKEN=
      volumes:
         - /var/run/docker.sock:/var/run/docker.sock
         - ./Traefik/traefik.toml:/traefik.toml
         - ./Traefik/acme.json:/acme.json
      labels: 
         - "traefik.backend=traefik"
         - "traefik.docker.network=traefik"
         - "traefik.enable=false"
         - "traefik.frontend.rule=Host:Host:monitor.gideonwolfe.com"
         - "traefik.port=8080"
         - "traefik.frontend.headers.forceSTSHeader=true"
         - "traefik.frontend.headers.STSSeconds=315360000"
         - "traefik.frontend.headers.STSIncludeSubdomains=true"
         - "traefik.frontend.headers.STSPreload=true"

   jellyfin:
      image: linuxserver/jellyfin
      container_name: jellyfin
      environment:
         - PUID=1000
         - PGID=1000
         - TZ=America/Los_Angeles
      volumes:
         - /home/gideon/Data/Programs/Docker/Jellyfin/ProgramData/:/config
         - /home/gideon/Data/Media/TV/:/data/tvshows
         - /home/gideon/Data/Media/Movies/:/data/movies
         - /home/gideon/Data/Music/:/data/music  
      ports:
         - 8096:8096
      labels:
         - "traefik.enable=true"
         - "traefik.port=8096"
         - "traefik.frontend.rule=Host:server.gideonwolfe.com"
         - "traefik.backend=JellyFin"
         - "traefik.frontend.entryPoints=https"
         - "traefik.frontend.headers.forceSTSHeader=true"
         - "traefik.frontend.headers.STSSeconds=315360000"
         - "traefik.frontend.headers.STSIncludeSubdomains=true"
         - "traefik.frontend.headers.STSPreload=true"
         - "traefik.docker.network=traefik"
      networks:
         - traefik
      restart: unless-stopped


   couchpotato:
      image: linuxserver/couchpotato
      container_name: couchpotato
      environment:
         - PUID=1000
         - PGID=1000
         - TZ=America/Los_Angeles
         - UMASK_SET=022
      volumes:
         - /home/gideon/Data/Programs/Docker/CouchPotato/config:/config
         - /home/gideon/Data/Programs/Docker/CouchPotato/Downloads:/downloads
         - /home/gideon/Data/Media/Movies:/movies # Where movies end up
      ports:
         - 5050:5050
      restart: unless-stopped
      labels:
         - "traefik.enable=false"

   sabnzbd:
      image: linuxserver/sabnzbd
      container_name: sabnzbd
      environment:
         - PUID=1000
         - PGID=1000
         - TZ=America/Los_Angeles
      volumes:
         - /home/gideon/Data/Programs/Docker/Sabnzbd/config:/config
         - /home/gideon/Data/Media/:/downloads
      ports:
         - 8080:8080
         - 9090:9090
      restart: unless-stopped
      labels:
         - "traefik.enable=false"

   sonarr:
      image: linuxserver/sonarr
      container_name: sonarr
      environment:
         - PUID=1000
         - PGID=1000
         - TZ=America/Los_Angeles
         - UMASK_SET=022 #optional
      volumes:
         - /home/gideon/Data/Programs/Docker/Sonarr/config:/config
         - /home/gideon/Data/Media/TV:/tv # Final folder where shows end up
         - /home/gideon/Data/Media/:/downloads # Same as SAB downloads
      ports:
         - 8989:8989
      restart: unless-stopped
      labels:
         - "traefik.enable=false"
networks:
  traefik:
    external: true   
  internal:
  • Each service level represents a containerized docker application.
  • The image level tells docker where to pull the image from.
  • The environment level allows you to pass variables into the container.
  • The volumes level mounts directories from the host into docker containers. This allows for persistant settings and access to media locations.
  • ports defines the port to access the services
  • labels allow us to define additional settings, such as disabling SSL certs with traefik.

I would recommend creating a seperate docker-compose.yaml for each sevice, and testing them ony by one before combining them into a master compose file.

Step 4: Web configuration

  • Jellyfin: localhost:8096

    • As long as your desired media is mounted in docker-compose.yaml, You can use the dashboard page to set up libraries with these folders. Piece of cake.
  • SABnzbd: localhost:8080

    • You need to connect SAB with a usenet provider using your account credentials and API key sent by your provider. This is done in the “Servers” settings tab.
    • Set up the Categories tab to sort downloads into appropriate folders. For example, tv goes to ./TV, movies to ./Movies. These puts them in the folders Jellyfin looks in.
  • Sonarr: localhost:8989

    • Allow Sonarr to rename episodes under ‘Media Management’
    • Under the “Indexers” tab, enter the url and API key from any indexers you have accounts for. Sign up for as many as possible for maximum success finding media.
    • Under “Download Client” tab, add SAB. You will need the local IP address of your SAB server as well as the API key found in the “General” section of the SAB settings.
    • Optionally enable metadata fetching
  • CouchPotato: localhost:5050

    • Add your indexers in the “Searchers” tab
    • Add SAB in the “Downloaders” tab
    • Allow Movie Renaming

After all this is set up, we should be able to test it with some media.

  1. Try finding a single TV episode through Sonarr using the magnifying glass.
  2. This should be sent to SABnzbd to download
  3. It should be in your media directory TV/Series/Season #/Episode
  4. Refresh Jellyfin and view the media

Once this pipeline is working correctly, add all the shows you want, and set up monitors so new episodes are automatically downloaded.

For CouchPotato, try adding a movie and seeing if the same behavior happens. Are your movies showing up in Jellyfin?

Conclusion

In this guide, I detailed the steps for setting up an end to end automated pipeline for downloading and streaming your favorite media. Although the initial setup can be tricky at times, the portability and convenience offered by docker makes this setup awesome!

Containers and services can easily be added to the configuration, such as Syncthing or Nextcloud for file backups and sharing.