Serving Plex Clients from a Raspberry Pi
Plex is the gold standard for a home-hosted media server, allowing you to stream your home movies or (legally) ripped media from any television, computer, or phone either at home or when out and about.
Plex is widely run across many homelabs, as it is easy to setup, and unlike some other projects actually provides good value to the end user. In the remainder of this post, I'll break down how I have my Plex server set up for 24/7 access on a Raspberry Pi.
Storage
Storing a copy of all your films and TV shows takes up a lot of space. Before deciding to go down the Plex route consider the total storage you'll need. Budget about 1GB/hr of HD TV content and up to 100GB per Blu-ray you want to rip. Also consider home videos and any other content you might want/need.
For my Plex server, I currently have a dedicated 8TB 3.5" HDD. This drive is not mirrored and as such will lose all content when it dies. For me, this is not a big issue, as the cost/benefit ratio of having a secondary drive for parity or a backup is more of a headache than simply re-ripping the disks I have.
My Plex drive is a ZFS-formatted drive, which means I can easily make it into a mirrored vdev
later down the line, and benefits from datasets and copy on write, amongst many other things. Besides this drive, I also have some others in operation for my NAS, but I've already done a bit of a deep dive into that on this blog, detailing the hardware in use and my 3-2-1 strategy for retention of important data.
I separate my content into a movies
directory, a tv-shows
directory, and pull in home videos from my YouTube archive, which I might discuss in another post.
Docker
When choosing how to run Plex, you can either go native, or run it in a Docker container. Interestingly, they also have a semi-official Kubernetes Helm chart, meaning that you can potentially make it highly available if you have a K8s cluster at your disposal.
On the official page, you can choose the network type for the server, either as a bridge device (giving the container its own networking, then exposing select ports), make use of a Macvlan (which assigns the computer another IP address specifically for the Plex server), or simply use the host network.
For my current setup, I found it simplest to just use the host network, as this meant fewer headaches getting the container running, and easier debugging later down the line.
Installation
To get started, I logged into my NAS server, running-lion
, and cloned the repository into my home directory:
britton-charlie@running-lion:~ $ git clone --depth 1 https://github.com/plexinc/pms-docker plexmediaserver
Cloning into 'plexmediaserver'...
remote: Enumerating objects: 50, done.
remote: Counting objects: 100% (50/50), done.
remote: Compressing objects: 100% (41/41), done.
remote: Total 50 (delta 1), reused 27 (delta 1), pack-reused 0
Receiving objects: 100% (50/50), 60.45 KiB | 578.00 KiB/s, done.
Resolving deltas: 100% (1/1), done.
Importing Configuration
Following this, I created a config
directory and copied my configuration from the previous server into this directory. This would mean all watch data and other settings would already be there when I started to use it. Configuration was copied from my computer as a zipped file, as there are many small files on disk which need expanding.
The configuration directory supplied to the docker-compose.yml
file needs to be at config
, which means that the files are at config/Library/Application Support/Plex Media Server/
.
Editing docker-compose.yml
In the repository, there are already sample configurations for each of the three network set ups. I renamed mine and set the variables for my time-zone, IP, etc.
As I am using a Raspberry Pi, and for some reason Plex don't host an image for the ARM architecture, if you try and run the service without building your local image, you'll encounter the infamous exec <program>: exec format error
.
Building the Image
To fix this, we need to build the container for the aarch64
architecture we're using:
armv7l
. To build this, we copy the instructions from the README in the repository, and then we wait...
Well that was easy wasn't it? Oh wait no for some reason the build script is broken:
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now
The command '/bin/sh -c if [ "${TARGETPLATFORM}" = 'linux/arm/v7' ]; then S6_OVERLAY_ARCH='armhf'; elif [ "${TARGETARCH}" = 'amd64' ]; then S6_OVERLAY_ARCH='amd64'; elif [ "${TARGETARCH}" = 'arm64' ]; then S6_OVERLAY_ARCH='aarch64'; fi && curl -J -L -o /tmp/s6-overlay-${S6_OVERLAY_ARCH}.tar.gz https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-${S6_OVERLAY_ARCH}.tar.gz && tar xzf /tmp/s6-overlay-${S6_OVERLAY_ARCH}.tar.gz -C / --exclude='./bin' && tar xzf /tmp/s6-overlay-${S6_OVERLAY_ARCH}.tar.gz -C /usr ./bin && rm -rf /tmp/* && rm -rf /var/tmp/*' returned a non-zero code: 2
If this is also the case for you, then manually download with wget
, then substitute a couple of lines in the build file to use the copy that's already downloaded, instead of letting the build script itself try.
wget https://github.com/just-containers/s6-overlay/releases/download/v2.2.0.3/s6-overlay-aarch64.tar.gz
Aside: making build context smaller
If you do this in the same order I did, and therefore have your config
directory all setup, then Docker will copy this all across to the daemon before it tries to build, which in my case meant it had to read 2GB every time I tried to fix the build context.
Finally, I saw the lines I like to see whenever fixing a build script:
After that, I ran docker-compose up
and waited for it to come to life. Alas, this wouldn't work and yet more debugging of the build script was required. Looking at the root/installBinary.sh
file and the Dockerfile
, it was clear that the custom build was failing somewhere. The quickest fix for this is to just go to the Plex website and get the Ubuntu ARMv8 distribution here, setting this link as the ARG URL
parameter in the Dockerfile
.
Luckily, only one more docker build
was needed, and fortunately this time their broken build script worked okay. Maybe I misread the instructions, but either way they were unclear and unhelpful when it broke.
Further Issues
Once I'd managed to get the server running, I had even more issues. As I network boot my Raspberry Pi, the latency on the database was really high. To somewhat alleviate this problem, I optimised the database in the settings. I then had to change the location of the movies and TV show libraries, as the import kept the paths from the old server.
Transcoding
The Raspberry Pi doesn't handle transcoding very well at 1080p and up. Therefore, to reduce the possibility that we have to transcode and thus probably not be able to stream on a TV, we need to either create optimised versions, or use something like PlexCleaner to ensure that everything can direct play.
Remote Access
Plex makes it easy to allow access outside the network, if you can login to the router and forward a port. From here, Plex manages access to the server through the specified port, but won't work unless manually setup.
To allow this to happen smoothly, you need to forward port 32400 from the internet to your local server IP.
Conclusions
This is a bit of a shorter one as it took somewhat longer to setup the server than first anticipated. Plex can be hosted on a Raspberry Pi within a Docker container if you put your mind to it, but it is a fair bit more cumbersome than just running it natively. Server installation is made harder by the issues with migrating, network booting the Pi, and the lack of processing power the Pi has, but allows 24/7 access as opposed to having a computer constantly on, and also means that less power is used watching your favourite TV shows.