PROJECTAdvanced

Homelab Media Server with Full ARR Stack

Deploy a complete self-hosted media automation system with Plex, Sonarr, Radarr, Prowlarr, and more. Includes Traefik reverse proxy, Authentik SSO, and...

Dylan H.

Infrastructure Engineering

February 3, 2026
7 min read
6-8 hours
Homelab Media Server with Full ARR Stack

Overview

This project guides you through deploying a complete, production-grade media automation system using Docker Compose. By the end, you'll have automated TV show downloads, movie management, subtitle fetching, and a beautiful dashboard - all protected by SSO and reverse proxy.

What We're Building

┌───────────────────────────────────────────────────────────────────────┐
│                        HOMELAB MEDIA SERVER                           │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│   Internet ──► CloudFlare ──► Traefik ──► Services                    │
│                                   │                                   │
│                              Authentik (SSO)                          │
│                                                                       │
├───────────────────────────────────────────────────────────────────────┤
│  MEDIA AUTOMATION        │  CONSUMPTION       │  MONITORING          │
│  ────────────────        │  ───────────       │  ──────────          │
│  • Sonarr (TV)           │  • Plex            │  • Prometheus        │
│  • Radarr (Movies)       │  • AudioBookshelf  │  • Grafana           │
│  • Lidarr (Music)        │  • Calibre-Web     │  • Uptime Kuma       │
│  • Prowlarr (Indexers)   │  • Komga (Comics)  │  • Tautulli          │
│  • Bazarr (Subtitles)    │                    │                      │
│  • Overseerr (Requests)  │                    │                      │
└───────────────────────────────────────────────────────────────────────┘

Prerequisites

Before starting, you should be familiar with:

Hardware Requirements

ComponentMinimumRecommended
CPU4 cores8+ cores
RAM16GB32GB+
Storage500GB SSD + HDD arrayNVMe + multiple HDDs
Network1Gbps2.5Gbps+

Architecture

Stack Composition

We deploy four independent stacks that can be managed separately:

# compose.yml - Master orchestration
include:
  - stack-core-infra.yml      # Traefik, Authentik, Homepage
  - stack-arr.yml             # Media automation
  - stack-media-books.yml     # Consumption services
  - stack-monitoring.yml      # Observability

Network Architecture

                    Public Internet
                          │
              ┌───────────▼───────────┐
              │   CloudFlare DNS      │
              │   *.yourdomain.com    │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │   Traefik (Proxy)     │
              │   :80, :443           │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │   Docker: proxy       │
              └─┬─────────────────────┘
                │
    ┌───────────┼───────────┬──────────────┐
    ▼           ▼           ▼              ▼
 Authentik   *arr Stack   Media Apps   Monitoring

Directory Structure

Create the base directory structure:

sudo mkdir -p /srv/docker-stack/{traefik,authentik,sonarr,radarr}
sudo mkdir -p /srv/docker-stack/{lidarr,bazarr,prowlarr,overseerr}
sudo mkdir -p /srv/docker-stack/{grafana,prometheus,loki,homepage}
sudo mkdir -p /mnt/dockerdata/{media,torrents}
sudo mkdir -p /mnt/dockerdata/media/{tv,movies,music,audiobooks}
 
# Set ownership
sudo chown -R 1000:1000 /srv/docker-stack
sudo chown -R 1000:1000 /mnt/dockerdata

Volume Mapping Pattern

All services follow a consistent pattern:

volumes:
  - /srv/docker-stack/<service>/config:/config  # Service config
  - /mnt/dockerdata/media:/mnt/media            # Media library
  - /mnt/dockerdata/torrents:/mnt/torrents      # Downloads

Step 1: Core Infrastructure

Create Docker Networks

docker network create proxy
docker network create monitoring

Traefik Configuration

File: /srv/docker-stack/traefik/traefik.yml

api:
  dashboard: true
  insecure: false
 
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
 
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy
  file:
    directory: /etc/traefik/dynamic
    watch: true
 
certificatesResolvers:
  cloudflare:
    acme:
      email: your-email@example.com
      storage: /acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Core Infrastructure Stack

File: /srv/docker-stack/stack-core-infra.yml

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 80:80
      - 443:443
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /srv/docker-stack/traefik/traefik.yml:/traefik.yml:ro
      - /srv/docker-stack/traefik/acme.json:/acme.json
      - /srv/docker-stack/traefik/dynamic:/etc/traefik/dynamic:ro
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)
      - traefik.http.routers.traefik.entrypoints=websecure
      - traefik.http.routers.traefik.tls.certresolver=cloudflare
      - traefik.http.routers.traefik.service=api@internal
    deploy:
      resources:
        limits:
          memory: 512M
 
  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - /srv/docker-stack/homepage/config:/app/config
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.homepage.rule=Host(`home.yourdomain.com`)
      - traefik.http.routers.homepage.entrypoints=websecure
      - traefik.http.routers.homepage.tls.certresolver=cloudflare
      - traefik.http.services.homepage.loadbalancer.server.port=3000
 
networks:
  proxy:
    external: true

Deploy Core Infrastructure

cd /srv/docker-stack
docker compose -f stack-core-infra.yml up -d
 
# Verify services
docker compose -f stack-core-infra.yml ps

Step 2: Media Automation Stack

Environment Configuration

File: /srv/docker-stack/.env

PUID=1000
PGID=1000
TZ=America/Edmonton
CF_DNS_API_TOKEN=your-cloudflare-token

ARR Stack Deployment

File: /srv/docker-stack/stack-arr.yml

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /srv/docker-stack/sonarr/config:/config
      - /mnt/dockerdata/media:/mnt/media
      - /mnt/dockerdata/torrents:/mnt/torrents
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.sonarr.rule=Host(`sonarr.yourdomain.com`)
      - traefik.http.routers.sonarr.entrypoints=websecure
      - traefik.http.routers.sonarr.tls.certresolver=cloudflare
      - traefik.http.services.sonarr.loadbalancer.server.port=8989
      - homepage.group=Media Automation
      - homepage.name=Sonarr
      - homepage.icon=sonarr
      - homepage.href=https://sonarr.yourdomain.com
    deploy:
      resources:
        limits:
          memory: 512M
 
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /srv/docker-stack/radarr/config:/config
      - /mnt/dockerdata/media:/mnt/media
      - /mnt/dockerdata/torrents:/mnt/torrents
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.radarr.rule=Host(`radarr.yourdomain.com`)
      - traefik.http.routers.radarr.entrypoints=websecure
      - traefik.http.routers.radarr.tls.certresolver=cloudflare
      - traefik.http.services.radarr.loadbalancer.server.port=7878
      - homepage.group=Media Automation
      - homepage.name=Radarr
      - homepage.icon=radarr
    deploy:
      resources:
        limits:
          memory: 512M
 
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /srv/docker-stack/prowlarr/config:/config
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.prowlarr.rule=Host(`prowlarr.yourdomain.com`)
      - traefik.http.routers.prowlarr.entrypoints=websecure
      - traefik.http.routers.prowlarr.tls.certresolver=cloudflare
      - traefik.http.services.prowlarr.loadbalancer.server.port=9696
      - homepage.group=Media Automation
      - homepage.name=Prowlarr
      - homepage.icon=prowlarr
    deploy:
      resources:
        limits:
          memory: 256M
 
  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /srv/docker-stack/bazarr/config:/config
      - /mnt/dockerdata/media:/mnt/media
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.bazarr.rule=Host(`bazarr.yourdomain.com`)
      - traefik.http.routers.bazarr.entrypoints=websecure
      - traefik.http.routers.bazarr.tls.certresolver=cloudflare
      - traefik.http.services.bazarr.loadbalancer.server.port=6767
      - homepage.group=Media Automation
      - homepage.name=Bazarr
      - homepage.icon=bazarr
    deploy:
      resources:
        limits:
          memory: 256M
 
  overseerr:
    image: sctx/overseerr:latest
    container_name: overseerr
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - TZ=${TZ}
    volumes:
      - /srv/docker-stack/overseerr/config:/app/config
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.overseerr.rule=Host(`requests.yourdomain.com`)
      - traefik.http.routers.overseerr.entrypoints=websecure
      - traefik.http.routers.overseerr.tls.certresolver=cloudflare
      - traefik.http.services.overseerr.loadbalancer.server.port=5055
      - homepage.group=Media
      - homepage.name=Overseerr
      - homepage.icon=overseerr
    deploy:
      resources:
        limits:
          memory: 512M
 
networks:
  proxy:
    external: true

Deploy ARR Stack

docker compose -f stack-arr.yml up -d

Step 3: Initial Configuration

Prowlarr Setup (Do First)

  1. Access https://prowlarr.yourdomain.com
  2. Set authentication (Settings → General → Authentication)
  3. Add indexers (Indexers → Add Indexer)
  4. Configure Apps to sync with Sonarr/Radarr

Sonarr Configuration

  1. Access https://sonarr.yourdomain.com
  2. Add root folder: /mnt/media/tv
  3. Configure download client
  4. Add quality profiles
  5. Import existing library or add series

Radarr Configuration

  1. Access https://radarr.yourdomain.com
  2. Add root folder: /mnt/media/movies
  3. Configure download client
  4. Set up quality profiles
  5. Add movies or import library

Bazarr Subtitle Setup

  1. Access https://bazarr.yourdomain.com
  2. Connect to Sonarr and Radarr
  3. Add subtitle providers (OpenSubtitles, etc.)
  4. Configure languages

Step 4: Monitoring Stack

See Network Monitoring Basics for detailed Prometheus/Grafana setup.

Quick deployment:

# stack-monitoring.yml (simplified)
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - /srv/docker-stack/prometheus/config:/etc/prometheus
      - /srv/docker-stack/prometheus/data:/prometheus
    networks:
      - proxy
      - monitoring
 
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    volumes:
      - /srv/docker-stack/grafana/data:/var/lib/grafana
    networks:
      - proxy
      - monitoring
    labels:
      - traefik.enable=true
      - traefik.http.routers.grafana.rule=Host(`grafana.yourdomain.com`)
 
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    volumes:
      - /srv/docker-stack/uptime-kuma:/app/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.uptime.rule=Host(`uptime.yourdomain.com`)
 
networks:
  proxy:
    external: true
  monitoring:
    driver: bridge

Security Considerations

Authentication Options

  1. Authentik SSO - Enterprise-grade, supports SAML/OIDC
  2. Authelia - Lighter alternative
  3. App-native auth - Each app's built-in authentication

Network Isolation

# Add to services that shouldn't be public
networks:
  - internal  # No traefik access
 
networks:
  internal:
    internal: true  # No external access

Secrets Management

Never commit secrets to git:

# .gitignore
.env
*/config/
acme.json
*_token.txt

Maintenance

Update All Containers

#!/bin/bash
# /srv/docker-stack/update.sh
 
cd /srv/docker-stack
 
for stack in stack-*.yml; do
  echo "Updating $stack..."
  docker compose -f "$stack" pull
  docker compose -f "$stack" up -d
done
 
# Cleanup old images
docker image prune -f

Backup Strategy

# Backup configs (exclude media)
tar -czvf homelab-backup-$(date +%Y%m%d).tar.gz \
  --exclude='*/cache/*' \
  --exclude='*/logs/*' \
  /srv/docker-stack/

For detailed setup of individual components:


Troubleshooting

Container Won't Start

# Check logs
docker logs <container_name>
 
# Check resource usage
docker stats

Traefik Certificate Issues

# Check ACME status
docker logs traefik 2>&1 | grep -i acme
 
# Verify DNS
dig +short sonarr.yourdomain.com

Permission Issues

# Reset ownership
sudo chown -R 1000:1000 /srv/docker-stack/<service>/config

Next Steps

  • Add Authentik for SSO across all services
  • Set up automated backups to cloud storage
  • Configure alerting in Grafana
  • Add Tautulli for Plex analytics