Part 2: Media Players - The Heart of Your System

Foundations of Sovereign Self-Hosting
Welcome to Part 2 of the Foundations of Sovereign Self-Hosting series. In this post, we’ll focus on setting up and configuring the media players that form the core of your media server stack. This includes Plex, Jellyfin, Navidrome, Audiobookshelf, and Mealie. Each application serves a unique purpose, and we’ll break down their features and use cases.
Disclaimer
Before we dig in, I want to acknowledge that my stack is heavily dependent on my custom network configuration. I’ve created Docker-specific bridge networks and run a separate network stack of containers that support this and other service stacks.
For example, I isolate qBittorrent on a casavpn
bridge alongside Gluetun and a container that syncs dynamic port forwarding between them. This lets qBittorrent connect through ProtonVPN and keep its port in sync automatically:
network_mode: "container:GLUETON" #change gluetun to your VPN container name
That’s a pretty specific use case—and yours may be simpler. To make these posts accessible, I’ll use generic network definitions in the sample Docker Compose files. I will, however, include my exact configs in the GitHub repo if you're curious or if it suits your needs.
I’ve spent a lot of time building a setup that lets me securely access internal services over the internet, with zero open ports, using a combination of Cloudflare Tunnel, Cloudflare DNS, and Traefik—plus Authentik (which I’m still undecided on… stay tuned for more on that).
Here is the basic layout just so you can visualize it for the future.....

Now let's get back to setting up our media stack.
Understanding the Media Players
After years of testing dozens of media players for movies, music, audiobooks, and photos, this is the stack that works best for me.
Plex - https://www.plex.tv
Plex is a polished, commercial media server for streaming movies, shows, music, and more. It offers metadata fetching, library organization, and remote access.
It’s not open source and does require a license—but I bought a lifetime license back in 2013, and I still think it’s worth it today. Plexamp has me seriously reconsidering my music setup.
Jellyfin - https://jellyfin.org/
Jellyfin is the open-source alternative to Plex. It covers all the same ground—movies, series, music—and even supports books (unlike Plex).
Jellyfin works great on my NVIDIA Shield, while Plex still wins on Apple TV. I’ve noticed it plays some media files better than Plex, too.
Navidrome - https://www.navidrome.org/
Navidrome is a lightweight music streaming server, compatible with Subsonic clients and backed by a robust API.
It's not the prettiest UI, but it's solid. I have 8+ months of music in my library, and I stream it using Amperfy on iOS.
Audiobookshelf - https://www.audiobookshelf.org/
This is the best self-hosted audiobook and podcast server I’ve used. Streaming, bookmarking, metadata—it just works.
Sadly, I missed the iOS beta, so I use Plappa as my mobile player, which is fantastic.
Mealie - https://mealie.io/
Mealie is a self-hosted recipe manager. Not technically a media player, but I include it in the media stack because I use it almost every day.
It’s clean, organized, and makes life easier in the kitchen.
Immich - https://immich.app/
Immich is a fantastic self-hosted photo/video manager. It’s not in my Compose stack here because I deploy it separately due to its complexity (PostgreSQL, Redis, etc.).
I’ll include my Immich Compose file in the GitHub repo—it mostly follows the official docs, aside from Traefik integration.
Alright now that we know what the containers we are setting up are for lets get to setting them up!
Setting Up and Configuring Media Players
Let's start with Plex
plex:
image: linuxserver/plex:latest
container_name: plex
network_mode: host
environment:
- PUID=1000
- PGID=1000
- VERSION=latest
- PLEX_CLAIM=YOUR_PLEX_CLAIM
volumes:
- /path/to/plex/config:/config
- /path/to/plex/transcode:/transcode
- /path/to/media:/media
restart: unless-stopped
Note: The /path/to/media
mount should follow TRaSH's File and Folder Structure Guide. My setup uses media/
instead of data/
at the root.
Ok, on to Jellyfin
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
network_mode: host
user: 1000:1000
volumes:
- /path/to/jellyfin/config:/config
- /path/to/jellyfin/cache:/cache
- /path/to/media:/media
restart: unless-stopped
Navidrome
navidrome:
image: deluan/navidrome:latest
container_name: navidrome
ports:
- "4533:4533"
environment:
- ND_LOGLEVEL=info
volumes:
- /path/to/navidrome/data:/data
- /path/to/music:/music
restart: unless-stopped
Audiobookshelf
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf
container_name: audiobookshelf
ports:
- "13378:80"
volumes:
- /path/to/audiobooks:/audiobooks
- /path/to/podcasts:/podcasts
- /path/to/audiobookshelf/config:/config
restart: unless-stopped
and last but NOT least Mealie
mealie:
image: ghcr.io/mealie-recipes/mealie:latest
container_name: mealie
ports:
- "9000:9000"
volumes:
- /path/to/mealie/data:/app/data
environment:
- ALLOW_SIGNUP=true
restart: unless-stopped
Full Docker Compose File (Part 2)
## Foundations of Sovereign Self-Hosting
## MEDIA SERVER STACK
## BASE DOCKER COMPOSE FILE - Part 2
#DEFINE OUR NETWORKS#
networks:
proxy: # Proxy Network to route through traefik
driver: bridge
name: proxy
lan: # General-purpose network
driver: bridge
name: lan
vpn: # VPN Network
driver: bridge
name: vpn
#DEFINE OUR SERVICES#
services:
plex:
image: linuxserver/plex:latest
container_name: plex
network_mode: host
environment:
- PUID=1000
- PGID=1000
- VERSION=latest
- PLEX_CLAIM=YOUR_PLEX_CLAIM
volumes:
- /path/to/plex/config:/config
- /path/to/plex/transcode:/transcode
- /path/to/media:/media
restart: unless-stopped
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
network_mode: host
user: 1000:1000
volumes:
- /path/to/jellyfin/config:/config
- /path/to/jellyfin/cache:/cache
- /path/to/media:/media
restart: unless-stopped
navidrome:
image: deluan/navidrome:latest
container_name: navidrome
networks:
- proxy
ports:
- "4533:4533"
environment:
- ND_LOGLEVEL=info
volumes:
- /path/to/navidrome/data:/data
- /path/to/music:/music
restart: unless-stopped
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf
container_name: audiobookshelf
ports:
- "13378:80"
networks:
- proxy
volumes:
- /path/to/audiobooks:/audiobooks
- /path/to/podcasts:/podcasts
- /path/to/audiobookshelf/config:/config
restart: unless-stopped
mealie:
image: ghcr.io/mealie-recipes/mealie:latest
container_name: mealie
networks:
- proxy
ports:
- "9000:9000"
volumes:
- /path/to/mealie/data:/app/data
environment:
- ALLOW_SIGNUP=true
restart: unless-stopped
Replace placeholders with actual paths and your PLEX_CLAIM key. Then launch with:
docker compose up -d
Check status:
docker ps
Testing Your Setup
Ensure each service is running by visiting the appropriate URLs:
- Plex:
http://localhost:32400
- Jellyfin:
http://localhost:8096
- Navidrome:
http://localhost:4533
- Audiobookshelf:
http://localhost:13378
- Mealie:
http://localhost:9000
Next Steps
In the next part, we will explore the 'Arr' ecosystem - the media managers that will connect to and organize your media libraries.
Stay tuned for the next post, and keep building your sovereign media server stack!