How I Built My Self-Hosted Media Stack

Self-hosting gives me full control over how I manage, consume, and organize digital media. Instead of relying on commercial streaming platforms, I run a fully automated media server on my own hardware — with each service containerized using Docker Compose.

Here’s an overview of the key apps in my setup and how they work together to automate everything from downloading to organizing and enriching my media library.


Homepage – My Homelab Dashboard

It all starts with a clean dashboard. I use Homepage as the front door to my self-hosted environment. It gives me quick access to all my services in a visually appealing layout, with real-time Docker stats and customizable widgets. It's minimal, fast, and easy to configure.


Radarr and Sonarr – Automated Media Management

Radarr and Sonarr form the core of my system, handling movies and TV shows respectively. They monitor upcoming releases, manage my watchlist, and automatically request downloads when new content becomes available. Once the content is downloaded, they organize everything into my media library with consistent naming and folder structure.


Prowlarr – Unified Indexer Management

Prowlarr connects everything to the outside world. It manages the indexers that Radarr and Sonarr use to search for content. Rather than configuring indexers in each app separately, Prowlarr centralizes that process, making everything easier to manage and maintain.


Bazarr – Subtitle Automation

To ensure everything I watch has accurate subtitles, I run Bazarr. It monitors my media library for missing or out-of-date subtitles and automatically fetches the best available versions. This is especially helpful for foreign content or when watching in noisy environments.


qBittorrent – Torrent Engine

All downloads go through qBittorrent, an open-source torrent client with a polished web interface. It works smoothly with Radarr, Sonarr, and Prowlarr to handle downloads efficiently. It’s lightweight, fast, and reliable.


FlareSolverr – Bypassing Web Protections

Some indexers use Cloudflare or other anti-bot protections, which can block automated tools like Prowlarr. FlareSolverr handles those challenges in the background, ensuring uninterrupted access to those sources.


MakeMKV – Ripping Physical Media

For converting DVDs and Blu-rays into digital files, I use MakeMKV. It provides a browser-based interface for ripping discs and outputs clean MKV files that can easily be added to my media library. It’s a great way to preserve old physical collections.


Unpackerr – Post-Processing and Cleanup

Once downloads complete, Unpackerr takes care of extracting and organizing any compressed files. It integrates with both Radarr and Sonarr to ensure media is in the right format and location, ready to be played without additional manual steps.


All Powered by Docker Compose

Every service runs as its own Docker container, I have put my compose and .env files below for your convenience.

Compose File:

#########################################################
networks:
  mediastack:
    name: mediastack
    driver: bridge
    ipam:
      driver: default
services:
  #########################################################

  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    restart: unless-stopped
    ports:
      - ${WEBUI_PORT_HOMEPAGE:?err}:3000
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/homepage:/app/config
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - PUID=0
      - PGID=0
      # - UMASK=${UMASK:?err}
      - TZ=${TIMEZONE:?err}
      - HOMEPAGE_ALLOWED_HOSTS=home.fenici.com.au
    networks:
      - mediastack
  #########################################################

  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/bazarr:/config
      - ${FOLDER_FOR_TORRENTS:?err}:/app/qBittorrent/downloads
      - ${FOLDER_FOR_MOVIES:?err}:/movies
      - ${FOLDER_FOR_TV_SHOWS:?err}:/tv/Tv_shows
    environment:
      - PUID=${PUID:?err}
      - PGID=${PGID:?err}
      - TZ=${TIMEZONE:?err}
      - DOCKER_MODS=ghcr.io/themepark-dev/theme.park:bazarr
      - TP_THEME=${TP_THEME:?err}
    ports:
      - ${WEBUI_PORT_BAZARR:?err}:6767
  #########################################################

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:develop
    container_name: prowlarr
    restart: unless-stopped
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/prowlarr:/config
    environment:
      - PUID=${PUID:?err}
      - PGID=${PGID:?err}
      - TZ=${TIMEZONE:?err}
      - DOCKER_MODS=ghcr.io/themepark-dev/theme.park:prowlarr
      - TP_THEME=${TP_THEME:?err}
    ports:
      - ${WEBUI_PORT_PROWLARR:?err}:9696
  #########################################################

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: unless-stopped
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/radarr:/config
      - ${FOLDER_FOR_TORRENTS:?err}:/app/qBittorrent/downloads
      - ${FOLDER_FOR_MOVIES:?err}:/movies/
    environment:
      - PUID=${PUID:?err}
      - PGID=${PGID:?err}
      - TZ=${TIMEZONE:?err}
      - DOCKER_MODS=ghcr.io/themepark-dev/theme.park:radarr
      - TP_THEME=${TP_THEME:?err}
    ports:
      - ${WEBUI_PORT_RADARR:?err}:7878
  #########################################################

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: unless-stopped
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/sonarr:/config
      - ${FOLDER_FOR_TORRENTS:?err}:/app/qBittorrent/downloads
      - ${FOLDER_FOR_TV_SHOWS:?err}:/tv/Tv_shows
      - /mnt/zenith/media/tv_shows:/tv/Tv_shows2
    environment:
      - PUID=${PUID:?err}
      - PGID=${PGID:?err}
      - TZ=${TIMEZONE:?err}
      - DOCKER_MODS=ghcr.io/themepark-dev/theme.park:sonarr
      - TP_THEME=${TP_THEME:?err}
    ports:
      - ${WEBUI_PORT_SONARR:?err}:8989
  #########################################################

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/qbittorrent:/config
      - ${FOLDER_FOR_TORRENTS:?err}:/app/qBittorrent/downloads
      - ${FOLDER_FOR_MEDIA:?err}:/app/qBittorrent/media
      - ${FOLDER_FOR_MOVIES:?err}:/movies/
      - ${FOLDER_FOR_TV_SHOWS:?err}:/tv/Tv_shows
    environment:
      - PUID=${PUID:?err}
      - PGID=${PGID:?err}
      # - UMASK=${UMASK:?err}
      - TZ=${TIMEZONE:?err}
      - WEBUI_PORT=${WEBUI_PORT_QBITTORRENT:?err}
      - TORRENTING_PORT=${QBIT_PORT:?err}
      - DOCKER_MODS=ghcr.io/themepark-dev/theme.park:qbittorrent
      - TP_THEME=${TP_THEME:?err}
    ports:
      - ${WEBUI_PORT_QBITTORRENT:?err}:8200
      - ${QBIT_PORT:?err}:6881
      - ${QBIT_PORT:?err}:6881/udp
  #########################################################

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    restart: unless-stopped
    environment:
      - LOG_LEVEL=info
      - LOG_HTML=false
      - CAPTCHA_SOLVER=none
      - TZ=${TIMEZONE:?err}
    ports:
      - ${FLARESOLVERR_PORT:?err}:8191
  #########################################################

  makemkv:
    image: jlesage/makemkv
    container_name: makemkv
    restart: unless-stopped
    ports:
      - ${MAKEMKV_PORT:?err}:5800
    volumes:
      - ${FOLDER_FOR_CONFIGS:?err}/makemkv/appdata:/config:rw
      - ${FOLDER_FOR_CONFIGS:?err}/makemkv/home/user:/storage:ro
      - ${FOLDER_FOR_CONFIGS:?err}/makemkv/home/output:/output:rw
      - ${FOLDER_FOR_MEDIA:?err}:/media

  #########################################################

  unpackerr:
    image: golift/unpackerr
    container_name: unpackerr
    volumes:
      - ${FOLDER_FOR_TORRENTS:?err}:/downloads
    restart: always
    user: ${PUID:?err}:${PGID:?err}
    environment:
    - TZ=${TIMEZONE:?err}
    ## Global Settings
    - UN_DEBUG=false
    - UN_QUIET=false
    - UN_ERROR_STDERR=false
    - UN_ACTIVITY=false
    - UN_LOG_QUEUES=1m
    - UN_LOG_FILE=/downloads/unpackerr.log
    - UN_LOG_FILES=10
    - UN_LOG_FILE_MB=10
    - UN_INTERVAL=2m
    - UN_START_DELAY=1m
    - UN_RETRY_DELAY=5m
    - UN_MAX_RETRIES=3
    - UN_PARALLEL=1
    - UN_FILE_MODE=0644
    - UN_DIR_MODE=0755
    ## Web Server
    - UN_WEBSERVER_LISTEN_ADDR=0.0.0.0:5656
    ## Folder Settings
    - UN_FOLDERS_INTERVAL=1s
    - UN_FOLDERS_BUFFER=20000
    ## Sonarr Settings
    - UN_SONARR_0_URL=http://192.168.50.221:8989
    - UN_SONARR_0_API_KEY=${SONARR_API_KEY:?err}
    - UN_SONARR_0_PATHS_0=/downloads
    - UN_SONARR_0_PROTOCOLS=torrent
    - UN_SONARR_0_TIMEOUT=10s
    - UN_SONARR_0_DELETE_DELAY=5m
    - UN_SONARR_0_DELETE_ORIG=false
    - UN_SONARR_0_SYNCTHING=false
    ## Radarr Settings
    - UN_RADARR_0_URL=http://192.168.50.221:7878
    - UN_RADARR_0_API_KEY=${RADARR_API_KEY:?err}
    - UN_RADARR_0_PATHS_0=/downloads
    - UN_RADARR_0_PROTOCOLS=torrent
    - UN_RADARR_0_TIMEOUT=10s
    - UN_RADARR_0_DELETE_DELAY=5m
    - UN_RADARR_0_DELETE_ORIG=false
    - UN_RADARR_0_SYNCTHING=false

Env File:

COMPOSE_PROJECT_NAME=mediastack
TP_THEME=overseerr

FOLDER_FOR_CONFIGS=/mnt/zenith/configs
FOLDER_FOR_MEDIA=/mnt/tank/media
FOLDER_FOR_MOVIES=/mnt/tank/media/Movies
FOLDER_FOR_TV_SHOWS=/mnt/tank/media/Tv_Shows
FOLDER_FOR_TORRENTS=/mnt/tank/torrents
PUID=568
PGID=568
TIMEZONE=Australia/Perth

# Backend Ports
QBIT_PORT=6881
FLARESOLVERR_PORT=8191
MAKEMKV_PORT=5800

# WebUI Ports
WEBUI_PORT_HOMEPAGE=3123
WEBUI_PORT_BAZARR=6767
WEBUI_PORT_PROWLARR=9696
WEBUI_PORT_RADARR=7878
WEBUI_PORT_SONARR=8989
WEBUI_PORT_QBITTORRENT=8200

# unpackerr options
SONARR_URL=
RADARR_URL=
SONARR_API_KEY=
RADARR_API_KEY=