Friday, March 22, 2019

Plex Docker Container on Fedora 29+ - Installation, Setup, Configuration

Just some notes on my deployment of Plex Media Server in Docker on Fedora 29+.

The Background

To make my Plex installation more modular and robust, I am moving one of my two Plex Media Server installations into a Docker container.  Since my host hardware is an old Lenovo T61p laptop with 8GB of memory, I went about two different configurations of making the installation of Plex Media Server more modular.

I recently upgraded the hard drive from a 500GB 7200 RPM SATA HDD to a 120GB SSD.  The spinning media had very high latency which limited the performance of the overall system.  Being remote most of the time, this made administration slow and painful while trying to manage uptime and availability.  With SSD costs coming down considerably lately, I was able to pick up an few new 120GB SSDs on Amazon for $20.  This prompted an OS upgrade from Fedora 28 to Fedora 29 (finally) and provided the motivation to rebuild this instance of my Plex Media Server.

Into the nuts and bolts of it.  The first method I went about was based on familiarity.  I deployed VirtualBox on the host and built a Fedora 29 VM with 4GB of memory and a 30GB virtual drive (VMDK) format, then proceeded to install and setup Plex.  The install and setup went as expected, just followed along with my other instruction posts.  The only major difference was that since my media is all stored on USB external drives, I had to set up persistent mapping to map the drives to the VM with the correct permissions in order to read all of the media files.

With a Plex VM setup and running, and all media mapped, it was time to do some performance testing, and this is where the cracks really started to show.  The server was very laggy, even for LAN connections, and the host CPU and memory utilization were almost pegged at 100%.  To continue down this path, I was going to need to upgrade the host, as this setup just didn't have enough horsepower to run basic services on the host, plus a VM on top of that.  Ok, cool enough, just export and archive the VM and look for another approach.

I have limited experience with Docker and Linux containers in general, so why not give this a shot.  When looking at the VM setup, it was clear that the VM itself, not so much Plex, was consuming a boatload of resources.  The thought process here is that if I remove the overhead of the VM I will only see a small uptick in resource consumption with a container over a base install.

First step was to get Docker installed on the host machine.  Just as a reference, I am using Docker Community Edition (Docker CE).  There are a bunch of tutorials out there, so I just followed along with the one from Docker here.

https://docs.docker.com/install/linux/docker-ce/fedora/

Install the Docker CE from repository

Setup the repository

$ sudo dnf install dnf-plugins-core

Use the following command to install the stable repository

$ sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo

Install Docker CE

$ sudo dnf install docker-ce docker-ce-cli containerd.io

Start Docker

$ sudo systemctl start docker

Verify that Docker CE is installed correctly by running the hello-world image.


$ sudo docker run hello-world

Configure Docker to start on boot

$ sudo systemctl enable docker

Now it is time to set up the Plex Docker container.  Following the documentation from the github repo, the setup continues along.

https://github.com/plexinc/pms-docker/blob/master/README.md

First step is to decide the networking configuration that will be used.  The three options are bridged, host, and macvlan.  I am going to use the host option as the setup is pretty easy and meets my needs for how Plex will be administered, both from the container level and the host level.  The host option sets up the container using the same networking as the host.  This will save a smidge on performance on the host as the bridged option sets up a separate local network on the host and runs containers from within it.

Start by pulling the latest Plex Media Server container from the repo

$ sudo docker pull plexinc/pms-docker

Here is where it gets a bit tricky, the configuration of the create command.  Most of these parameters will vary based on your configuration, but the documentation on the Docker Docs site is pretty clear, so customize to your heart's content.

https://docs.docker.com/engine/reference/commandline/create/

Note 1 - User and Group ID - get the User and Group ID with issuing

$ id <username>

          uid=1000(<username>) gid=1000(<username>)   groups=1000(<username>),10(wheel),976(vboxusers)

This returns the UID of 1000 and GID of 1000 for my user.  Set with the -e settings, if you set the PLEX_UID and PLEX_GID to 1000, then the permissions will match that of my user.  Using the permissions of my user eliminates with permissions issues of accessing my media on the volume binding  -v options and allows the plex user to be able to read, write, and execute on the /config and /transcode directories without issue.

Note 2 - Time Zone - You can set the time -e TZ=<timeZone> environment variable as any time zone value, but it's easier to keep consistency by setting as the same time zone as the host.  To get the current time zone from the host system, use the following command

$ timedatectl
          Local time: Fri 2019-03-22 15:07:52 EDT
          Universal time: Fri 2019-03-22 19:07:52 UTC
          RTC time: Fri 2019-03-22 19:07:52
          Time zone: America/New_York (EDT, -0400)
          System clock synchronized: yes
          NTP service: active
          RTC in local TZ: no

and note the value in the Time zone: field.  For this installation, I want to keep my time zone as Americas/New_York.

Note 3 - Volume binding - You need to have unique volume bindings, so if sourcing media from multiple sources, you need to provide unique volume binding paths.  For my installation, I have media across five USB external drives, so I need to provide unique volume binding for each media source.

The paths to my media are as follows:

/run/media/<username>/Drive2/Media2/Media/TV
/run/media/<username>/Drive4/Media4/Media/TV
/run/media/<username>/Drive4/Media6/Media/TV
/run/media/<username>/Drive5/Media8/Media/TV
/run/media/<username>/Drive3/Media3/Media/MOVIES

/run/media/<username>/Drive3/Media5/Media/MOVIES
/run/media/<username>/Drive5/Media7/Media/MOVIES
/run/media/<username>/Drive1/Media1/Media/MUSIC

so my volume bindings would be:

-v /run/media/<username>/Drive2/Media2/Media/TV:/data/TV_2
-v /run/media/<username>/Drive4/Media4/Media/TV:/data/TV_4
-v /run/media/<username>/Drive4/Media6/Media/TV:/data/TV_6
-v /run/media/<username>/Drive5/Media8/Media/TV:/data/TV_8
-v /run/media/<username>/Drive3/Media3/Media/MOVIES:/data/MOVIES_3
-v /run/media/<username>/Drive3/Media5/Media/MOVIES:/data/MOVIES_5
-v /run/media/<username>/Drive5/Media7/Media/MOVIES:/data/MOVIES_7
-v /run/media/<username>/Drive1/Media1/Media/MUSIC:/data/MUSIC

and Plex Docker specific volume bindings:

-v /home/<username>/docker/plex/plexmediaserver:/config
-v /home/<username>/docker/plex/transcode:/transcode

The last two volume bindings can be anything that you choose.  These are where I want all of the container data (/config) including the database and where transcoding temp files (/transcode) are stored.  These files can get quite large, depending on library files and number of concurrent users, so make sure this location has plenty of available storage space.

Note 4 - Plex Claim - The claim token required for the server to obtain a real server token.  If not provided, server will not be automatically logged in. If server is already logged in, this parameter is ignored. You can obtain a claim token to login your server to your plex account by visiting https://www.plex.tv/claim

Note 5 - --restart option - I have selected the container to not restart automatically for this installation.  Keeping in mind that my media is stored on external USB hard drives with LUKS encryption.  When the host starts up, the persistent drive mappings of the drive don't resolve since the partitions aren't mounted in the host OS yet.  I could write a script that mounts these at boot and have the container start automatically, maybe i'll get to it someday, but for now, I need to have all of the storage manually mounted first before starting the container.  If the container starts without the storage mounted, then the container will create directories for the volume bindings, and after I mount the partitions on the external drives, they will have different paths and my media won't be available to the container.

Now to create the container with my installation-specific parameters:

$ sudo docker create --name=plex \
--net=host \
--restart=no \
-e VERSION=latest \
-e PLEX_UID=1000 \
-e PLEX_GID=1000 \
-e TZ=America/New_York \
-e PLEX_CLAIM=claim-xxxxxxxxxxxxxxxxxxxx \
-v /home/<username>/docker/plex/plexmediaserver:/config \
-v /run/media/<username>/Drive2/Media2/Media/TV:/data/TV_2 \
-v /run/media/<username>/Drive4/Media4/Media/TV:/data/TV_4 \
-v /run/media/<username>/Drive4/Media6/Media/TV:/data/TV_6 \
-v /run/media/<username>/Drive5/Media8/Media/TV:/data/TV_8 \
-v /run/media/<username>/Drive3/Media3/Media/MOVIES:/data/MOVIES_3 \
-v /run/media/<username>/Drive3/Media5/Media/MOVIES:/data/MOVIES_5 \
-v /run/media/<username>/Drive5/Media7/Media/MOVIES:/data/MOVIES_7 \
-v /run/media/<username>/Drive1/Media1/Media/MUSIC:/data/MUSIC \
-v /home/<username>/docker/plex/transcode:/transcode \
plexinc/pms-docker

If the create command is successful, Docker will output the container ID.  It will look something like this:

f62433dfbd6b92124b4dc9b2e2981189d629a1c224ba3d663281ebf10eddc0a6

Almost there.  Now we need to make sure that the default Plex service on the host firewall for the active zone is active.

First, I get the default zone

$ sudo firewall-cmd --get-default-zone
public

then check which services are currently enabled in the default zone:

$ sudo firewall-cmd --list-services
dhcpv6-client mdns ssh

the response did not have the plex service listed, so no Plex traffic would be allowed from the LAN and port forwarding from WAN would not work.  Rather than adding a specific port and protocol in the firewall, plex is a predefined service in the firewall application, so I just added plex as a service to the permanent configuration.

$ sudo firewall-cmd --add-service=plex --permanent

Then reload the firewall with:

$ sudo firewall-cmd --reload

And check to see if the port is now exposed with

$ sudo firewall-cmd --zone=public --list-services
dhcpv6-client mdns plex ssh

Time to start the container:

$ sudo docker start plex

Response: 

plex

It will take a few seconds for the container to initialize and start (my install takes about a minute to be fully up).  Since this is the first setup, there is a pretty major caveat...

You have to be on the LAN in order to do the initial setup.  This is great if you are physically present, or have something like TeamViewer installed, but if you are not physically present, you will need to use SSHuttle to set up a local connection to the host system, see SSH as a VPN.

With SSHuttle connected, in a browser enter the host ip and port to access the Plex web interface

http://<ip address>:32400/web/index.html

I had to use my Plex login credentials to access the web interface and complete the setup.

At this point, everything worked perfectly with the setup.  From within the Plex web interface, I was able to log in to my server, add media libraries since I made all of the volume bindings to the host media storage in the docker create step, and see all of the Plex statistics.

The last portion of the setup is making sure that this instance on Plex Media Server is accessible outside of my local network.  I have WAN port 32400 forwarded in my router to my host machine ip address and LAN port 32400.  Remote access didn't come up straight off, but after a few minutes it popped up and said it was available.  I tested this by trying to access this installation of Plex Media Server from my phone over the mobile network, and it worked.

Note 6 - Manually Specify Public Port - For whatever reason, Plex defaulted to using port 0 for external access.  Once I clicked 'Manually specify public port' and set it at 32400, clicked 'Save Changes', it was externally available.

Check the status after a few minutes

$ sudo docker container ls -l

CONTAINER ID    IMAGE                 COMMAND           CREATED         STATUS                      PORTS         NAMES

f62433dfbd6b    plexinc/pms-docker    "/init"           1 hour ago      Up 1 hour (healthy)                     plex

To display a live stream of container(s) resources usage statistic, use

$ sudo docker container stats plex

CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS

f62433dfbd6b        plex                0.14%               20.21MiB / 3.786GiB   0.52%               0B / 0B             616GB / 684MB       64

This was under idle load, and this is while streaming a movie to an external device

$ sudo docker container stats plex

CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
f62433dfbd6b        plex                96.92%              84.53MiB / 3.786GiB   2.18%               0B / 0B             616GB / 684MB       78

I am not overly worried about the high CPU usage since this Plex instance gets light use from only a few users, but the host also has a dual core CPU installed, so only one core is handling the transcoding and the other core is handling minimal host tasks and only running around 10%.

Since auto restart for the container is not enabled, and Docker is set to start with the host OS, I tried resetting the host, authenticating and mounting my remote storage, and starting the Plex Docker container.

To update the Plex Docker container, run the following commands:

Stop the Plex Docker container.
$ sudo docker stop plex

Remove the existing docker container
$ sudo docker rm plex

Pull the new Plex docker image from the Docker Hub repo
$ sudo docker pull plexinc/pms-docker

Then run the Docker create statement above to create a new Docker container with a new claim code.


No comments:

Post a Comment