My Unnecessary Adventure in (Re)Building a Media Sever

Welcome! Strap in because this is gonna be a long ride.

To preface, I had an existing media server that I broke some permissions and reinstalling was the only option. I had an existing RAID 0 setup with lots of data I did not want to lose, plus a standalone SSD. So, without further adieu, here we go!

Installing Ubuntu Server

I chose Ubuntu Server because Ubuntu has a great deal of community support and I have no need for a desktop GUI.

Download the ISO from here. You may have to choose 'Option 3' to actually get a download. At the time of writing, I'm using 20.04.1, so take that into account.

My tool of choice for burning ISOs is balenaEtcher.

Plug in the USB, boot to it, and follow setup. Make sure you enable the SSH server AND NO LVM ON THE SSD. Check to make sure the full capacity of the SSD is mounted to / and then you're good to go. Finish the setup, remove the USB, and reboot. Now we'll switch to SSH.

Configuring SSH

Port 22 sucks, but we need to login with it. Connect to the server on port 22 with the credentials you added during install.

Open the port in the firewall

sudo ufw allow 8364/tcp

Open the SSH config file and change the port used

sudo nano /etc/ssh/sshd_config

Find the line containing Port 22, uncomment, and change to Port 8364. Save (ctrl x).

Restart the SSH server and then reconnect on your client.

sudo systemctl restart ssh

Drive Mounting

Find the UUID of the drive:

sudo blkid

Add your drive to /etc/fstab (BE CAREFUL, THIS CAN BRICK STUFF):

sudo nano /etc/fstab


UUID=ccc56606-385e-4a16-89c4-7f1513ab1641 /home ext4 defaults 0 0

Reboot the system and your drive your be properly mounted.


First, update your system:

sudo apt update
sudo apt upgrade

Instead of repeating things, go follow the official Docker tutorial here. Just follow the 'Install using the repository' and 'Install Docker Engine' sections.

Then, install Docker Compose by following the tutorial here.

The next part is taken mostly from Smart Home Beginner.

We will add the current user to the docker group to simplify some things in the future.

sudo gpasswd -a $USER docker

Activate the changes using the following:

newgrp docker


Portainer is our docker management solution. Yes, I have serious issues with it but currently don't have an alternative. Maybe this will change in the future, but here we are.

Run the following commands to get Portainer up and running:

docker volume create portainer_data
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
docker run -d -p 9001:9001 --name portainer_agent --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes portainer/agent

Open a browser and go to your computer's IP with the port 9000. For me, that's Chose a username and password, then create user.

Choose "Docker" and press "Connect".

Now that you are in the web panel, click on 'local' in the middle, and then the 'Stacks' tab on the left. Click 'Add stack' and give it a name such as "main_stack". Paste in the following config and adjust it to fit your needs.

version: "2.3"

    image: linuxserver/organizr
    container_name: organizr
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - ${USERDIR}/organizr:/config
      - 9983:80
    restart: unless-stopped

    container_name: watchtower
    restart: always
    image: containrrr/watchtower
      - /var/run/docker.sock:/var/run/docker.sock
    command: --schedule "0 0 4 * * *" --cleanup

    image: linuxserver/radarr
    container_name: radarr
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - ${USERDIR}/radarr:/config
      - ${USERDIR}/media/movies:/movies
      - ${USERDIR}/downloads:/downloads
      - 7878:7878
    restart: unless-stopped

    image: linuxserver/sonarr
    container_name: sonarr
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - ${USERDIR}/sonarr:/config
      - ${USERDIR}/media/tv:/tv
      - ${USERDIR}/downloads:/downloads
      - 8989:8989
    restart: unless-stopped

    image: linuxserver/duckdns
    container_name: duckdns
      - TZ=${TZ}
      - SUBDOMAINS=kyleserver
      - TOKEN=326ab158-e842-4d0e-aa20-233e0f7e51e3
    restart: unless-stopped

    container_name: minecraft-creative
    image: itzg/minecraft-server
      EULA: "true"
      VERSION: 1.16.2
      CONSOLE: "false"
      LEVEL_TYPE: "flat"
      - 25566:25565
      - ${USERDIR}/minecraft-creative:/data
    restart: unless-stopped

    container_name: minecraft-survival
    image: itzg/minecraft-server
      EULA: "true"
      VERSION: 1.16.2
      CONSOLE: "false"
      MEMORY: 2G
      - 25565:25565
      - ${USERDIR}/minecraft-survival:/data
    restart: unless-stopped

    image: linuxserver/qbittorrent:
    container_name: qbittorrent
    network_mode: "service:gluetun"
      - ${USERDIR}/docker/qbittorrent:/config
      - ${USERDIR}/downloads:/downloads
    restart: unless-stopped
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - UMASK_SET=002
      - WEBUI_PORT=4545

    image: qmcgaw/private-internet-access
    container_name: gluetun
      - NET_ADMIN
    network_mode: bridge
      - 8888:8888
      - 8388:8388
      - 8388:8388/udp # Shadowsocks
      - 8001:8001
      - 4545:4545
      - 6881:6881
      - 6881:6881/udp
      - ${USERDIR}/gluetun:/gluetun
      - VPNSP=surfshark
      - TZ=${TZ}
      - REGION=Canada Toronto
    restart: unless-stopped

Next, run id and note your uid and pid. We will input these as PUID and PGID in the "Environment" section.


Add any environment variables that you need NOW as I don't believe these can be changed in the future.

Click 'Deploy the stack'.

Currently, qbittorrent has an issue with mismatched torrent names, that's why it's using a custom image version. If it gets resolved in the future, remove everything after the colon.

These are simply the containers I use for everything I do. The 'gluetun' container connects to Surfshark VPN and routes the qbittorrent container's traffic through, allowing a normal container to connect to a VPN. This can be done with any container by moving its ports to gluetun and setting its network_mode to "service:gluetun". After that, go and check each container to make sure it's working.


Welp, this is gonna be a fun one. If you don't want to do hardware transcoding with a GPU, simply add a standard container to the above compose file. Otherwise, come along for this hell of a ride. There is no guarantee that some of these steps aren't redundant as I have no clue how any of this works. But, ideally, everything will work by the end of this.

Firstly, we need to install the NVIDIA drivers. I'm using a GTX 1050ti but it should work for any NVIDIA card 10-series or newer.

Run these commands first to disable Nouveau:

sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf"

What is Nouveau and why are we disabling it? How dare you ask! Subsequently, I have no clue but it's what we gotta do.


Install DMKS:

sudo apt install dkms

Follow the instructions here to install the drivers. Additional Notes:

  • Update the version number in the other commands
  • Each command needs the be run with sudo

Click 'Yes' for everything, and after exiting the GUI, reboot, then run the test commands.

Install ffmpeg:

sudo apt install ffmpeg

Next, we need to setup the NVIDIA Container Toolkit. Follow the official instructions here.

Now the GPU should be good to go! Next, we need to actually setup Jellyfin. Going back to SSH, go to your home dir (cd).

Create a new docker compose file called docker-compose.yml and fill it with the following:

version: "2.3"
    image: linuxserver/jellyfin
    container_name: jellyfin
    network_mode: "host"
    runtime: nvidia
      - PUID=1000
      - PGID=1000
      - TZ=America/Toronto
      - /home/user/jellyfin:/config
      - /home/user/media/tv:/data/tvshows
      - /home/user/media/movies:/data/movies
      - 8080:8096
      - 7359:7359/udp
    restart: unless-stopped

Run docker-compose up -d and Jellyfin will be running. Counterintuitvly, go to port 8096 on your server's IP and login.

Change Jellyfin Settings

  • Go to Settings > Advanced > Networking. Change "Local HTTP port number" and "Public HTTP port number" to 8080.
  • Go to Server > Playback and under "Hardware acceleration" choose 'Nvidia NVENC'. Select which files you want to decode.
  • Change 'ffmpeg path' to /usr/bin

Restart Jellyfin from Portainer, and you're good to go!


It was a lot of work, but hopefully everything works. Remember, DuckDuckGo and StackOverflow are your friends. If something doesn't work, let me know below and I'll do my best to help.

Good luck and stay curious!


No Comments Yet