Sections

  1. Introduction
  2. Installing Docker/Docker Compose
  3. Installing NVIDIA Driver
  4. Installing NVIDIA Container Toolkit
  5. Patch GPU To Have No Transcode Limit
  6. Install Plex Media Server Container
  7. Final Setup + Sanity Check

Introduction

I’ve recently started hosting my own Plex server to consolidate my media and allow ease of access when I am not at home. I have since started allowing my friends access to the server, but with this change came it’s own set of problems. I have some 4k files on the server that take quite a bit of resources to transcode. I had a spare GPU lying around that I wanted to use for transcoding, however, I also already have a more powerful GPU in the computer that I do not want Plex to use.

After digging around the internet, here is what I came up with.

Installing Docker/Docker Compose

It seems like this is only possible through Docker, so we must first set up Docker. Additionally, I want to use Docker Compose just to make things a little more manageable for future me.

Since my machine is running Ubuntu, I just had to follow Docker’s official install guide, but here’s a quick run through.

Spin up a terminal and set up apt by running the following commands:

sudo apt-get update

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

Then add Docker’s GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Set up Docker’s stable repo:

echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Finally, update apt and install Docker;

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io

Now Docker’s all installed!

To install Docker Compose just download the binary, put it in the right place, and make it an executable:

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

Note: 1.29.1 was the current release as of writing. Go to the Docker Compose website for the current release.

Installing NVIDIA Driver

Before we install the container, you’ll want to have the NVIDIA Driver installed. This can usually be done through your distributions repositories or by downloading the driver from NVIDIA.

Installing through your linux distro is usually reccomended as things don’t break as much, however, I’ll be installing through nvidia in this example for more control on versions.

On another computer go to https://www.nvidia.com/Download/index.aspx and put in your computer info and click search. Search with server settings

On the next page click download. Click download

Then you’ll be taken to a final page with another download button. Right click this final button and copy the link. You should have a link that ends in .run:

https://us.download.nvidia.com/XFree86/Linux-x86_64/460.73.01/NVIDIA-Linux-x86_64-460.73.01.run

Now, let’s install the driver on the server. First, we download the driver with the link we just got:

wget https://us.download.nvidia.com/XFree86/Linux-x86_64/460.73.01/NVIDIA-Linux-x86_64-460.73.01.run

sudo chmod +x NVIDIA-Linux-x86_64-460.73.01.run

Finally, we can run the installer and install the driver:

sudo ./NVIDIA-Linux-x86_64-460.73.01.run

Note: You might need to reinstall the driver when you update your distro, as there are no hook to re-enable the driver module when updating.

Check if GPU(s) are showing up:

nvidia-smi

This should output something like this:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.39       Driver Version: 460.39       CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1070    Off  | 00000000:01:00.0 Off |                  N/A |
| N/A   34C    P2    10W / 120W |      0MiB /  8117MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  GeForce RTX 3070    Off  | 00000000:02:00.0 Off |                  N/A |
| N/A   32C    P2     8W / 135W |      0MiB /  7982MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

Installing NVIDIA Container Toolkit

The NVIDIA Container Toolkit is needed to allow other Docker containers access to the GPU.

First we need to set up the repo and GPG key:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
   && curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
   && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

After we have the repo all set up, we can install the nvidia-docker2 package:

sudo apt update

sudo apt install -y nvidia-docker2

Then we need to restart Docker:

sudo systemctl restart docker

Next we’ll start the Docker container with access to all GPUs, we will restrict GPU usage when we set up the Plex Docker container. (You can limit GPU access at this step if you want. This is detailed in the NVIDIA user guide)

sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

This should output all the GPU(s) available to use for Plex:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.39       Driver Version: 460.39       CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1070    Off  | 00000000:01:00.0 Off |                  N/A |
| N/A   34C    P2    10W / 120W |      0MiB /  8117MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  GeForce RTX 3070    Off  | 00000000:02:00.0 Off |                  N/A |
| N/A   32C    P2     8W / 135W |      0MiB /  7982MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

In my case, you can see my primary GPU and the GPU I want to use to transcode. If you restricted GPU(s) at this point, only the GPUs selected should appear.

Patch GPU To Have No Transcode Limit

Time for a quick intermission. Since the GPU I want to use is not a Quadro and NVIDIA has stupid limitations on how many streams you can encode at once, we’ll have to do some shenanigans to be able to encode more than 2 streams at once.

Thankfully, there are smart people out there who have developed a patch for the linux driver.

We can just git clone the repo and run the script:

git clone https://github.com/keylase/nvidia-patch

cd nvidia-patch

./patch

Install Plex Media Server Container

Time for the final piece of the puzzle!

Go to the location you want to install Plex and we’re going to set up the folder structure:

mkdir plex

mkdir plex/config

mkdir plex/transcode

touch plex/docker-compose.yml

Next, let’s find the GPU UUID of the GPU we want to use:

nvidia-smi --query-gpu=gpu_name,gpu_uuid --format=csv

This should output something like this:

GeForce GTX 1070, GPU-c83c365d-e87b-4d79-a7e6-33801e361c03
GeForce RTX 3070, GPU-b2917f53-38ed-4c2e-82c9-461193ba001f

Copy down the UUID of the GPU you want to use, as we’ll need it for our docker compose file.

Go into the plex folder and edit the docker-compose.yml:

cd plex

sudo vim docker-compose.yml

Hit i to start editing and paste the following:

---
version: "3.7"
services:
  plex:
    image: ghcr.io/linuxserver/plex
    container_name: plex
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
      - UMASK_SET=022
      - VERSION=docker
      - NVIDIA_VISIBLE_DEVICES=GPU-c83c365d-e87b-4d79-a7e6-33801e361c03
      - PLEX_CLAIM=CLAIM_TOKEN
    volumes:
      - /pathToPlexDir/config:/config
      - /pathToPlexDir/transcode:/transcode
      - /pathToMovies/Movies:/movies
      - /pathToShows/Shows:/tv
    restart: unless-stopped
    runtime: nvidia

Let’s break down the environment and volume variables a bit.

The most important one we want to look at is NVIDIA_VISIBLE_DEVICES. This is where we define which GPU Plex should use. Paste the GPU UUID we previously found here.

PUID and PGID are the corresponding user and group id you want the container to run under. To find these values you can run these commands:

id -u username # get's user id

id -g username # gets group id

TZ represents the timezone you want to use.

UMASK_SET sets permissions (Read more about it here).

VERSION tells us what version of Plex we’re using.

PLEX_CALIM let’s you claim your Plex server right off the bat without needing to go into settings. You can get one from https://www.plex.tv/claim/.

For the volume variables we set the /config and /transcode to the two directories we just created, and we can set the /movies and /tv paths to wherever our media is located on the server.

After you finish configuring everything, type :wq to save and close the file.

Now we can start the container:

docker-compose up -d

Plex should now be up and running!

Final Setup + Sanity Check

Go to the ip address of your server at port 32400 on your browser to finish setiing up Plex (i.e. http://192.168.1.160:32400).

If that page is stuck on spinning after you log in, you can also reach the server by going to https://app.plex.tv/.

Now that your Plex server is set up, you can add your media by pointing Movies and TV to the folders we created for them /movies and /tv.

Finally, let’s enable GPU transcoding by going to Settings > Traanscoder check the boxes next to Use hardware acceleration when available and Use hardware-accelerated video encoding. Then click save changes.

At this point everything should be running correctly, but let’s double check just for our sanity. Let’s open up a 4K movie and set the conversion to 720p HD 2 Mbps just for fun. Before we set this up, this would have stuttered and taken a long time, but it should now load fairly quickly as we’re using GPU transcoding instead of our CPU.

We can double check that our GPU is indeed being used and that the right one is being used:

nvidia-smi

Which should give us an output similar to this:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.39       Driver Version: 460.39       CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1070    Off  | 00000000:01:00.0 Off |                  N/A |
| 35%   63C    P2    54W / 120W |    961MiB /  8117MiB |     10%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  GeForce RTX 3070    Off  | 00000000:02:00.0 Off |                  N/A |
|  0%   44C    P8    16W / 135W |     11MiB /  7982MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      1215      G   /usr/lib/xorg/Xorg                 20MiB |
|    0   N/A  N/A    185355      C   ...diaserver/Plex Transcoder      936MiB |
|    1   N/A  N/A      1215      G   /usr/lib/xorg/Xorg                  9MiB |
+-----------------------------------------------------------------------------+

As you can see, Plex Transcoder is being used by GPU 0 which is the GTX 1070 I wanted to use!

Now you can transcode whatever you want and even use Plex in a browser relatively pain free.

Here’s a handy list of apporximately how many streams different GPUs can transcode: https://www.elpamsoft.com/?p=Plex-Hardware-Transcoding

Resources

  1. Docker Install (https://docs.docker.com/engine/install/ubuntu/)
  2. Docker Compose Install (https://docs.docker.com/compose/install/)
  3. NVIDIA Container Toolkit (https://github.com/NVIDIA/nvidia-docker)
  4. NVIDIA Encoding Patch (https://github.com/keylase/nvidia-patch)
  5. Plex Docker Install (https://github.com/linuxserver/docker-plex)