Three weeks ago, you were paying monthly subscriptions to Apple Music, Netflix, HBO, Libro.fm, and countless other services. Now, you’re running a self-hosted media server that gives you complete control over your content library. This transformation represents more than just cost savings—it’s a fundamental shift in how we consume and manage digital media.
The journey from subscription services to self-hosted solutions has become increasingly popular among tech-savvy users who value privacy, control, and long-term cost efficiency. With the rise of powerful yet affordable hardware like the Raspberry Pi 5 and mini PCs, combined with user-friendly software stacks, anyone can build their own media empire.
In this comprehensive guide, we’ll walk through the exact steps taken to migrate from subscription services to a fully self-hosted media ecosystem. We’ll cover hardware selection, software installation, configuration, and optimization—everything you need to know to take control of your digital media consumption.
Self-hosted media solutions represent a paradigm shift in how we consume digital content. Instead of paying recurring subscription fees and being subject to content availability changes, you maintain complete control over your media library. This approach offers several compelling advantages:
Cost Efficiency: While there’s an initial investment in hardware and setup time, the long-term savings are substantial. Consider that a typical household might spend $50-100 monthly on various streaming services. A well-configured media server can pay for itself within months.
Content Control: Your media library remains intact regardless of licensing changes or service discontinuations. You decide what to keep, how to organize it, and when to access it.
Privacy: Self-hosted solutions eliminate third-party tracking and data collection associated with commercial streaming services.
Customization: You can tailor your media experience to your exact preferences, from user interfaces to automated organization systems.
The technology stack for self-hosted media has matured significantly over the past few years. Modern solutions offer enterprise-grade features while maintaining user-friendly interfaces. The ARR (Automated Rarities) stack—comprising Sonarr, Radarr, and their companion tools—has become the de facto standard for automated media management.
Hardware Requirements and Selection
Core Server Hardware
The foundation of any self-hosted media server is reliable hardware. For this setup, we’ll focus on two primary options: the Raspberry Pi 5 and mini PCs.
Raspberry Pi 5 Configuration:
1
2
3
4
5
6
| # Recommended specifications for media server
Raspberry Pi 5 Model
- 8GB RAM (minimum 4GB)
- 32GB or larger microSD card
- Active cooling solution (heatsink + fan)
- 5V/3A USB-C power supply
|
Mini PC Configuration:
1
2
3
4
5
6
7
| # Higher-end option for demanding workloads
Mini PC Specifications
- Intel i3/i5 or AMD Ryzen 3/5 processor
- 16GB DDR4 RAM (minimum 8GB)
- 256GB NVMe SSD for OS
- 2.5" drive bays for storage
- Gigabit Ethernet port
|
Storage Considerations
Storage is critical for media servers. Calculate your needs based on current library size and expected growth:
1
2
3
4
5
6
7
| # Storage calculation example
Movies: 4TB (1000+ films at 4GB each)
TV Shows: 6TB (200+ series at 30GB per season)
Music: 500GB (10,000+ albums)
Audiobooks: 200GB (500+ titles)
Podcasts: 100GB (10 years of subscriptions)
Total: ~11TB minimum
|
Recommended Storage Setup:
- 2x 8TB WD Red NAS drives (RAID 1 for redundancy)
- 1x 500GB SSD for cache and applications
- Optional: Additional drives for future expansion
Network Infrastructure
A robust network is essential for media streaming:
1
2
3
4
5
6
7
| Network Requirements:
Bandwidth: Minimum 100Mbps, Recommended 1Gbps
Router: Gigabit with QoS support
Switch: Managed switch for VLAN separation
WiFi: 802.11ac/ax for wireless clients
Static IP: Recommended for server
Port Forwarding: Required for remote access
|
Software Stack Installation
Operating System Setup
Raspberry Pi OS (64-bit) Installation:
1
2
3
4
5
6
7
8
9
10
11
| # Flash the SD card with Raspberry Pi OS
wget https://downloads.raspberrypi.org/raspios_arm64/images/raspios_arm64-2024-01-08/2024-01-08-raspios-bullseye-arm64.img.xz
unzip 2024-01-08-raspios-bullseye-arm64.img.xz
sudo dd if=2024-01-08-raspios-bullseye-arm64.img of=/dev/sdX bs=4M status=progress conv=fsync
# Initial configuration
sudo raspi-config
# - Set locale and timezone
# - Expand filesystem
# - Enable SSH
# - Set hostname (plex-server)
|
Ubuntu Server Installation (for Mini PC):
1
2
3
4
5
6
7
8
| # Create bootable USB
sudo mkusb -p /path/to/ubuntu-22.04.2-live-server-amd64.iso /dev/sdX
# During installation:
# - Select minimal installation
# - Configure static IP
# - Create user with sudo privileges
# - Install OpenSSH server
|
Docker and Docker Compose Setup
Containerization provides isolation and easy management:
1
2
3
4
5
6
7
8
9
10
11
12
| # Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/2.18.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Verify installation
docker --version
docker-compose --version
|
Plex Media Server Configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # docker-compose.yml for Plex
version: '3.8'
services:
plex:
image: plexinc/pms-docker:latest
container_name: plex
restart: unless-stopped
network_mode: host
environment:
- PLEX_UID=1000
- PLEX_GID=1000
- TZ=America/New_York
- ADVERTISE_IP=http://server.local:32400/
volumes:
- ./config/plex:/config
- ./transcode:/transcode
- /path/to/movies:/data/movies
- /path/to/tv:/data/tv
- /path/to/music:/data/music
|
Sonarr (TV Shows) Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # docker-compose.yml for Sonarr
version: '3.8'
services:
sonarr:
image: hotio/sonarr:latest
container_name: sonarr
restart: unless-stopped
ports:
- "8989:8989"
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./config/sonarr:/config
- /path/to/tv:/tv
- /path/to/downloads:/downloads
|
Radarr (Movies) Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # docker-compose.yml for Radarr
version: '3.8'
services:
radarr:
image: hotio/radarr:latest
container_name: radarr
restart: unless-stopped
ports:
- "7878:7878"
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./config/radarr:/config
- /path/to/movies:/movies
- /path/to/downloads:/downloads
|
Lidarr (Music) Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # docker-compose.yml for Lidarr
version: '3.8'
services:
lidarr:
image: hotio/lidarr:latest
container_name: lidarr
restart: unless-stopped
ports:
- "8686:8686"
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./config/lidarr:/config
- /path/to/music:/music
- /path/to/downloads:/downloads
|
Booksonic (Audiobooks) Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # docker-compose.yml for Booksonic
version: '3.8'
services:
booksonic:
image: linuxserver/booksonic:latest
container_name: booksonic
restart: unless-stopped
ports:
- "4040:4040"
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./config/booksonic:/config
- /path/to/audiobooks:/audiobooks
|
Download Client Configuration
Transmission Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # docker-compose.yml for Transmission
version: '3.8'
services:
transmission:
image: linuxserver/transmission:latest
container_name: transmission
restart: unless-stopped
ports:
- "9091:9091"
- "51413:51413"
- "51413:51413/udp"
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- TRANSMISSION_WEB_HOME=/combustion-release/
- USER=username
- PASS=password
volumes:
- ./config/transmission:/config
- /path/to/downloads:/downloads
- /path/to/watch:/watch
|
Network Configuration and Security
Port Forwarding Setup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # Port forwarding configuration for router
# Plex Media Server
32400 (TCP) - Plex Web App
32469 (TCP/UDP) - DLNA
3005 (TCP) - Plex Companion
8324 (TCP) - Plex Companion
32412, 32413, 32414 (UDP) - Plex DLNA
# Sonarr
8989 (TCP) - Web Interface
# Radarr
7878 (TCP) - Web Interface
# Lidarr
8686 (TCP) - Web Interface
# Booksonic
4040 (TCP) - Web Interface
# Transmission
9091 (TCP) - Web Interface
51413 (TCP/UDP) - Peer Connections
|
Firewall Configuration
1
2
3
4
5
6
7
8
9
10
| # UFW firewall rules
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 32400/tcp comment 'Plex Media Server'
sudo ufw allow 8989/tcp comment 'Sonarr'
sudo ufw allow 7878/tcp comment 'Radarr'
sudo ufw allow 8686/tcp comment 'Lidarr'
sudo ufw allow 4040/tcp comment 'Booksonic'
sudo ufw enable
|
SSL/TLS Setup with Let’s Encrypt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # docker-compose.yml with SSL
version: '3.8'
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:latest
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx/vhost.d:/etc/nginx/vhost.d
- ./nginx/html:/usr/share/nginx/html
- ./letsencrypt:/etc/nginx/certs:ro
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion:latest
container_name: letsencrypt
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/etc/nginx/certs:rw
- ./nginx/vhost.d:/etc/nginx/vhost.d
- ./nginx/html:/usr/share/nginx/html
environment:
- NGINX_PROXY_CONTAINER=nginx-proxy
|
Directory Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # Recommended media directory structure
/media/
├── movies/
│ ├── Action/
│ ├── Comedy/
│ ├── Drama/
│ └── Sci-Fi/
├── tv/
│ ├── Show Name (2020)/
│ │ ├── Season 01/
│ │ ├── Season 02/
│ │ └── Specials/
│ └── Another Show/
├── music/
│ ├── Artist Name/
│ │ ├── Album Name/
│ │ └── Album Name/
│ └── Various Artists/
├── audiobooks/
│ ├── Author Name/
│ │ ├── Series Name/
│ │ └── Standalone Titles/
│ └── Various Authors/
└── podcasts/
├── Podcast Name/
└── Podcast Name/
|
File Naming Conventions
Movies:
1
2
| Movie.Name.(Year).Resolution.Codec.Audio.Extension
Example: Inception.(2010).1080p.BluRay.x264.DTS-HD.mkv
|
TV Shows:
1
2
| Show.Name.S01E01.Episode.Title.Resolution.Codec.Extension
Example: Breaking.Bad.S01E01.Pilot.1080p.BluRay.x264.mkv
|
Music:
1
2
3
4
| Artist - Album (Year)/
├── 01 - Track Name.flac
├── 02 - Track Name.flac
└── cover.jpg
|
Automation and Monitoring
Automated Downloads with Sonarr/Radarr
Sonarr Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Sonarr indexers and download clients
Indexers:
- Jackett (NZB)
- Torrent RSS feeds
Quality Profiles:
- Any (SD)
- SD (480p)
- HD 720p
- HD 1080p
- HD 1080p (Proper)
- Ultra HD 4K
Automatic Processing:
- Rename files
- Move to correct location
- Update Plex library
- Download subtitles
|
Radarr Setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Radarr quality profiles
Profiles:
- Custom (1080p Bluray)
- Custom (4K Bluray)
- Custom (3D Bluray)
Monitored:
- All movies
- Specific titles
- By folder
Automatic Actions:
- Rename and organize
- Notify Plex
- Download metadata
|
Monitoring with Prometheus and Grafana
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| # docker-compose.yml for monitoring
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./config/prometheus:/etc/prometheus
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./config/grafana:/etc/grafana/provisioning
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
|
Backup and Disaster Recovery
Backup Strategy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # Automated backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/path/to/backups"
CONFIGS_DIR="/path/to/configs"
MEDIA_DIR="/path/to/media"
# Backup configurations
tar -czf $BACKUP_DIR/configs_$DATE.tar.gz $CONFIGS_DIR
# Backup media database
sqlite3 /path/to/plex/database.db .dump > $BACKUP_DIR/plex_db_$DATE.sql
# Sync to external drive
rsync -avh --delete $MEDIA_DIR /path/to/external_drive/
# Clean old backups (keep last 7 days)
find $BACKUP_DIR -name "configs_*.tar.gz" -mtime +7 -delete
find $BACKUP_DIR -name "plex_db_*.sql" -mtime +7 -delete
|
Disaster Recovery Plan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| Recovery Procedures:
Primary Server Failure:
- Restore from latest backup
- Rebuild Docker containers
- Restore configurations
- Verify media integrity
Data Corruption:
- Check RAID status
- Restore from backup
- Verify file checksums
- Rebuild media library
Network Issues:
- Check port forwarding
- Verify firewall rules
- Test connectivity
- Restart services
|
1
2
3
4
5
6
7
8
| # Plex transcoding settings
Transcoder settings:
Hardware acceleration: Enabled (if supported)
Transcoder temporary directory: SSD
Maximum simultaneous transcodes: 2-3
Transcoding quality: Prefer higher speed
Allow video stream copying: Yes
Allow audio stream copying: Yes
|
Storage Optimization
1
2
3
4
5
6
| # ZFS pool configuration for optimal performance
zpool create media raidz2 /dev/disk1 /dev/disk2 /dev/disk3 /dev/disk4 /dev/disk5 /dev/disk6
zfs set compression=lz4 media
zfs set atime=off media
zfs set dedup=off media
zfs set recordsize=1M media
|
Network Optimization
1
2
3
4
5
6
| # Network tuning for media streaming
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216
sudo sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216'
sudo sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216'
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
|
Advanced Features and Integrations
Mobile Sync and Offline Access
1
2
3
4
5
6
| Mobile Sync Configuration:
Enable: Yes
Quality: Original (WiFi), Optimized (Mobile)
Download limit: 50GB per device
Sync duration: 30 days
Automatic cleanup: Yes
|
Smart Home Integration
1
2
3
4
5
6
7
8
9
10
11
12
| # Home Assistant integration
media_player:
- platform: plex
host: 192.168.1.100
port: 32400
name: Plex Media Server
username: !secret plex_username
password: !secret plex_password
timeout: 30
show_all_controls: false
include_non_clients: true
scan_interval: 5
|
API Integration and Automation
1
2
3
4
5
6
7
| # Python script for automated library management
import requests
import json
import time
PLEX_URL = "http://localhost:32400"
PLEX_TOKEN = "your_plex_token
|