4 min read

Part 2: Media Players - The Heart of Your System

Part 2: Media Players - The Heart of Your System
Media Players stack in Homepage

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 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!