/htpc-docker

Home server setup

Primary LanguageShell

Home media server

Overview

This server configuration provides the ability to download media using BitTorrent (via with VPN support) and/or Usenet. All media is automatically organized and served on the network by Plex Media Server.

All services in this guide are run in Docker containers managed via Docker Compose.

Service Description
plex Plex Organizes media and streams to smart devices
wireguard Wireguard VPN Tunnel
qbittorrent qBittorrent Torrent download client
sabnzbd SABnzbd Usenet download client
radarr Radarr Movie collection manager, integrates with qBittorrent/SABnzbd
sonarr Sonarr TV show collection manager, integrates with qBittorrent/SABnzbd
prowlarr Prowlarr Manages Torrent and Usenet indexers, integrates with Radarr/Sonarr
bazarr Bazarr Manages and downloads subtitles, integrates with Radarr/Sonarr
overseerr Overseerr Request management and media discovery tool for the Plex ecosystem
tautulli Tautulli Monitor Plex media server and track various statistics

The remainder of this guide assumes the host server is running a Debian-based operating system. My current setup is listed below:

Platform Optiplex 5060 Micro
Operating System Ubuntu Server 22.04
Processor Intel Core i5-8500T 2.1GHz
RAM 16GB DDR4 RAM
Internal Storage 256GB M.2 NVMe SSD
External Storage 2TB USB 2.0 HDD
Network 1Gbps Ethernet port

Prerequisites

Install Docker engine

Install using the convenience script:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh

Run the Linux post-installation steps to add your user to the docker group:

sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

Install SSH server

Note: Not required for Ubuntu Server when openssh-server was installed at setup time

To allow remote access via SSH, install and configure openssh on the host:

# Install openssh
sudo apt install openssh-server

# Enable service
sudo systemctl enable ssh --now

# Start service
sudo systemctl start ssh

Test the SSH connection by logging in from another computer on the network:

ssh <host-username>@<host-ip>

Optionally, you can install avahi to enable connections using the server's hostname:

sudo apt install avahi-daemon

# Now you can connect to <server-name>.local
ssh <host-username>@<server-hostname>.local

Enable remote desktop

Optional

Note: Not required for Ubuntu Server since no desktop environment is installed

To allow remote desktop access to the server from another computer, install and configure xrdp on the host.

sudo apt update && sudo apt install xrdp
sudo systemctl enable xrdp
sudo ufw allow 3389/tcp

Install the Microsoft Remote Desktop application on any client computers you with to connect from.

Set up directories

Note: The instructions below are designed for local storage on the host filesystem. To store media on a network share or USB drive, see the Additional notes section. It is still recommended to store the /data/config directory on the host filesystem.

Run the setup.sh script on the host to create the pre-determined directory structure with the correct permissions. These folders will be used as volume mounts for the Docker containers, and be referenced in each service's settings.

git clone git@github.com:nbn22385/htpc-docker.git
cd htpc-docker
./setup.sh
Expand to see the resulting folder structure

The resulting directory structure for service configuration files:

/config
├── bazarr
├── duckdns
├── grafana
├── nginx
├── overseerr
├── plex
├── prowlarr
├── qbittorrent
├── radarr
├── sabnzbd
├── sonarr
├── tautulli
└── wireguard

The resulting directory structure for data/media files:

/data
├── media
│   ├── movies
│   └── tv
├── torrents
│   ├── movies
│   └── tv
└── usenet
    ├── movies
    └── tv

Set up services

Services are managed via Docker using docker-compose.

Note: The base paths for service configuration and data are set within the .env file. Review the .env file and set CONFIG_ROOT and DATA_ROOT to your preferred paths.

Docker secrets

Create the following files in a secrets/ directory with the following values for the required services:

  1. Create a secrets/ directory if it does not exist. This folder will be ignored by git.
mkdir -p secrets
  1. DuckDNS

Create a secret for your DuckDNS token. For more info see the DuckDNS section of this document.

printf "DUCKDNS-TOKEN" > ./secrets/duckdns_token.txt
  1. Plex

Create a secret for the following Plex values.

  • PLEX-USERNAME: your Plex username.

  • PLEX-TOKEN: see these instructions for finding your Plex token.

  • PLEX-ADDRESS: The URL of your Plex server. For example, http://<SERVER-IP>:32400

    printf "PLEX-USERNAME" > ./secrets/plex_user.txt
    printf "PLEX-TOKEN" > ./secrets/plex_token.txt
    printf "PLEX-ADDRESS" > ./secrets/plex_address.txt
  1. Real-Debrid

Create a secret for your Real-Debrid API key, which can be found here.

printf "REAL-DEBRID-API-KEY" > ./secrets/rd_api_key.txt

VPN configuration

Optional (enabled by default)

Note: To disable use of a VPN for qBittorrent connections (not recommended), remove the network_mode line from the qbittorrent service in docker-compose.yml. Configuration updates will need to be made to Prowlarr, Radarr, and Sonarr download client settings, by pointing the server to qbittorrent, rather than wireguard.

In order to use the Wireguard docker image, a Wireguard configuration file must exist on the host as /config/wireguard/wg0.cfg. In many cases, the configuration file can be generated by following the instructions specific to your VPN provider.

Update the [Interface] section of your config file to allow incoming traffic from the host (update the HOMENET IP as needed for your network) (Source):

[Interface]
# I had to modify this line to reach the web interface
DNS: 8.8.8.8
# Exclude the docker subnet from being routed via Wireguard
PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route add $HOMENET3 via $DROUTE;ip route add $HOMENET2 via $DROUTE; ip route add $HOMENET via $DROUTE;iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT; iptables -A OUTPUT -d $HOMENET3 -j ACCEPT;  iptables -A OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route delete $HOMENET; ip route delete $HOMENET2; ip route delete $HOMENET3; iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; iptables -D OUTPUT -d $HOMENET -j ACCEPT; iptables -D OUTPUT -d $HOMENET2 -j ACCEPT; iptables -D OUTPUT -d $HOMENET3 -j ACCEPT

A note about Private Internet Access (PIA) VPN

For PIA VPN users, a config file is not provided and must be generated. Use the instructions in this repository to generate a configuration file. Once generated, ensure the output file is named wg0.conf, then copy it to the directory noted above.

Start the services

cd /path/to/htpc-docker
docker compose up -d

Access web UI for services

Once all services are running, you can access each service's web UI using the following URLs:

Service Role URL
Plex Media server http://<server-ip>:32400/web
qBitTorrent Torrent client http://<server-ip>:8080
SABnzbd Usenet client http://<server-ip>:8081
Radarr Movie manager http://<server-ip>:7878
Sonarr TV show manager http://<server-ip>:8989
Prowlarr Indexer manager http://<server-ip>:9696
Bazarr Subtitle manager http://<server-ip>:6767
Overseerr Request manager http://<server-ip>:5055
Tautulli Notification manager http://<server-ip>:8181

Manual service configuration

Settings must be manually configured for each service to properly use the directory structure we set up, as well as adjust other behaviors and integrations. I have listed the most important settings below, but adjust anything else as needed.

Plex

These custom settings are adapted from TRaSH Guide for Plex.

Plex settings
  • Settings
    • General
      • 🔳 Send crash reports to Plex
      • 🔳 Enable Plex Media Server debug logging
    • Remote Access (Optional)
      • ☑️ Remote access (Note: may need to forward router port 32400 to the host)
      • ☑️ Manually specify public port: 32400
    • Library
      • ☑️ Scan my library automatically
      • ☑️ Run a partial scan when changes are detected
      • ☑️ Run scanner tasks at a lower priority
  • Manage
    • Libraries
      • Movies
        • Manage Recommendations
          • Disable any recommendations you don't want to the client screen
        • Edit Library > Add Folders
          • Ensure /media/movies is listed
      • TV
        • Manage Recommendations
          • Disable any recommendations you don't want to the client screen
        • Edit Library > Add Folders
          • Ensure /media/tv is listed

qBittorrent

These custom settings are adapted from TRaSH Guide for qBittorrent.

qBittorrent settings
  • Settings
    • Downloads
      • ☑️ Delete .torrent files afterwards
      • ☑️ Pre-allocate disk space for all files
      • Default torrent management mode: Automatic
      • Default save path: /data/torrents
      • ☑️ (Optional) Email notification upon download completion
    • Connection
      • Peer connection protocol: TCP
    • Speed
      • Global Rate Limits
        • Set limits here if desired
    • WebUI
      • ☑️ Bypass authentication for clients on localhost
      • ☑️ Bypass authentication for clients in whitelisted IP subnets
        • Example: 192.168.1.0/24
      • ☑️ (Optional) Use alternative WebUI
        • VueTorrent already exists in the container. To use it, set this path to /vuetorrent.
    • Tags & Categories
      • Add category movies with save path /data/torrents/movies
      • Add category tv with save path /data/torrents/tv
    • Advanced
      • Network Interface: wg0 (this is the Wireguard interface)

Click the 💾 icon to apply the changes. Then (optionally) apply the custom VueTorrent settings:

  • Settings
    • VueTorrent
      • Copy/paste the following code and click the Import Settings button
        {"sort_options":{"isCustomSortEnabled":false,"sort":"completion_on","reverse":false,"filter":null,"category":null,"tag":null,"tracker":null},"webuiSettings":{"lang":"en","darkTheme":true,"showFreeSpace":true,"showSpeedGraph":true,"showSessionStat":true,"showAlltimeStat":true,"showCurrentSpeed":true,"showTrackerFilter":false,"showSpeedInTitle":false,"deleteWithFiles":true,"title":"Global Speed","rightDrawer":false,"topPagination":false,"paginationSize":15,"dateFormat":"DD/MM/YYYY, HH:mm:ss","openSideBarOnStart":false,"showShutdownButton":false,"useBitSpeed":false,"useBinaryUnits":false,"refreshInterval":2000,"contentInterval":5000,"torrentPieceCountRenderThreshold":5000,"busyDesktopTorrentProperties":[{"name":"Status","active":true},{"name":"Category","active":true},{"name":"Size","active":true},{"name":"Progress","active":true},{"name":"DownloadSpeed","active":true},{"name":"Downloaded","active":true},{"name":"SavePath","active":false},{"name":"UploadSpeed","active":true},{"name":"Uploaded","active":false},{"name":"ETA","active":true},{"name":"Peers","active":true},{"name":"Seeds","active":true},{"name":"Ratio","active":false},{"name":"Tracker","active":true},{"name":"Tags","active":false},{"name":"AddedOn","active":false},{"name":"Availability","active":false},{"name":"LastActivity","active":false},{"name":"CompletedOn","active":false},{"name":"AmountLeft","active":false},{"name":"ContentPath","active":false},{"name":"DownloadedSession","active":false},{"name":"DownloadLimit","active":false},{"name":"DownloadPath","active":false},{"name":"Hash","active":false},{"name":"InfoHashV1","active":false},{"name":"InfoHashV2","active":false},{"name":"SeenComplete","active":false},{"name":"TimeActive","active":false},{"name":"TotalSize","active":false},{"name":"TrackersCount","active":false},{"name":"UploadedSession","active":false},{"name":"UploadLimit","active":false},{"name":"GlobalSpeed","active":false},{"name":"GlobalVolume","active":false}],"doneDesktopTorrentProperties":[{"name":"Status","active":true},{"name":"Category","active":false},{"name":"Size","active":true},{"name":"Progress","active":false},{"name":"DownloadSpeed","active":false},{"name":"Downloaded","active":false},{"name":"SavePath","active":false},{"name":"UploadSpeed","active":true},{"name":"Uploaded","active":true},{"name":"ETA","active":false},{"name":"Peers","active":true},{"name":"Seeds","active":true},{"name":"Ratio","active":true},{"name":"Tracker","active":true},{"name":"Tags","active":false},{"name":"AddedOn","active":false},{"name":"Availability","active":false},{"name":"LastActivity","active":false},{"name":"CompletedOn","active":false},{"name":"AmountLeft","active":false},{"name":"ContentPath","active":false},{"name":"DownloadedSession","active":false},{"name":"DownloadLimit","active":false},{"name":"DownloadPath","active":false},{"name":"Hash","active":false},{"name":"InfoHashV1","active":false},{"name":"InfoHashV2","active":false},{"name":"SeenComplete","active":false},{"name":"TimeActive","active":true},{"name":"TotalSize","active":false},{"name":"TrackersCount","active":false},{"name":"UploadedSession","active":false},{"name":"UploadLimit","active":false},{"name":"GlobalSpeed","active":false},{"name":"GlobalVolume","active":false}],"busyMobileCardProperties":[{"name":"Status","active":true},{"name":"Tracker","active":false},{"name":"Category","active":true},{"name":"Tags","active":false},{"name":"Size","active":true},{"name":"Progress","active":true},{"name":"ProgressBar","active":true},{"name":"Ratio","active":false},{"name":"Uploaded","active":false},{"name":"ETA","active":true},{"name":"Seeds","active":true},{"name":"Peers","active":true},{"name":"DownloadSpeed","active":true},{"name":"UploadSpeed","active":true}],"doneMobileCardProperties":[{"name":"Status","active":true},{"name":"Tracker","active":false},{"name":"Category","active":true},{"name":"Tags","active":false},{"name":"Size","active":true},{"name":"Progress","active":true},{"name":"ProgressBar","active":true},{"name":"Ratio","active":false},{"name":"Uploaded","active":false},{"name":"ETA","active":true},{"name":"Seeds","active":true},{"name":"Peers","active":true},{"name":"DownloadSpeed","active":true},{"name":"UploadSpeed","active":true}]},"authenticated":true}

SABnzbd

These custom settings are adapted from TRaSH Guide for SABnzbd.

SABnzbd settings
  • Config
    • Folders
      • Temporary Download Folder: /data/usenet/incomplete
      • Completed Download Folder: /data/usenet
      • Click Save Changes
    • Servers
      • Click + Add Server
        • Enter the Host, Username, and Password for your Usenet provider
        • Test and add the server
    • Categories
      • Note: these paths are relative to the Completed Download Folder set above
      • For the movies category, set the Folder/Path to movies, click Save
      • For the tv category, set the Folder/Path to tv, click Save
    • Special
      • Values
        • host_whitelist(): Add an entry for the hostname sabnzbd
          • Note: This is necessary to allow access from Radarr/Sonarr

Radarr

These custom settings are adapted from TRaSH Guide for Radarr.

Radarr settings
  • Settings
    • Media Management
      • ☑️ Rename Movies
      • ☑️ Replace Illegal Characters
      • Root Folders
        • Add an entry for /data/media/movies
    • Profiles
      • Quality Profiles
        • I set up separate profiles for 1080p and 720p
      • Delay Profiles
        • I set up a delay profile "Prefer torrent" with a Usenet delay of 2 minutes. An opposite configuration can be set up for torrents. Currently I have Overseerr using this tag
      • Release Profiles
        • I set up an entry with keywords to avoid (i.e. "HDR")
    • Indexers
      • The indexers will auto-populate once Prowlarr is set up
      • Click Show Advanced
      • RSS Sync Interval: 0 (prevents hitting indexer API limits)
    • Download Clients
      • Click + and select qBittorrent
        • Host: wireguard
        • Username/Password: Use qbittorrent credentials
        • Category: movies
      • Click + and select SABnzbd
        • Host: sabnzbd
        • Port: 8080 (even though the web interface is 8081)
        • Api Key: SABnzbd API key from its Config > General (Security) page
        • Category: movies
        • 🔳 Remove Completed (I like to preserve SABnzbd history)
    • Import Lists (Optional, allows initiating downloads via the Plex Discover interface)
      • Click + and select Plex Watchlist
      • ☑️ Enable
      • ☑️ Enable Automatic Add
      • Monitor: select None to manually manage files in Radarr, otherwise leave default value
      • ☑️ Search on Add: enable to have Radarr automatically search for movie, otherwise leave default value
      • Quality Profile: select desired profile
      • Authenticate with Plex.tv: click button to authenticate
        • Note: Clicking Test will produce a warning if your Plex watchlist contains no movies
      • Click Save

Sonarr

These custom settings are adapted from TRaSH Guide for Sonarr.

Sonarr settings
  • Settings
    • Media Management
      • ☑️ Rename Episodes
      • ☑️ Replace Illegal Characters
      • Root Folders
        • Add an entry for /data/media/tv
    • Indexers
      • The indexers will auto-populate once Prowlarr is set up
      • Click Show Advanced
      • RSS Sync Interval: 0 (prevents hitting indexer API limits)
    • Download Clients
      • Click + and select qBittorrent
        • Host: wireguard
        • Username/Password: Use qbittorrent credentials
        • Category: tv
      • Click + and select SABnzbd
        • Host: sabnzbd
        • Port: 8080 (even though the web interface is 8081)
        • Api Key: SABnzbd API key from its Config > General (Security) page
        • Category: tv
        • 🔳 Remove Completed (I like to preserve SABnzbd history)
    • Import Lists (Optional, allows initiating downloads via the Plex Discover interface)
      • Click + and select Plex Watchlist
      • ☑️ Enable Automatic Add
      • Monitor: select None to manually manage files in Sonarr, otherwise leave default value
      • Quality Profile: select desired profile
      • Authenticate with Plex.tv: click button to authenticate
        • Note: Clicking Test will produce a warning if your Plex watchlist contains no shows
      • Click Save

Prowlarr

These custom settings are adapted from Servarr Guide for Prowlarr.

Prowlarr settings
  • Settings
    • Click Show Advanced (enables changing API limits below)
    • Apps
      • Applications
      • Sync Profiles
        • Click + (this will be used later in the Indexers configuration)
          • Name: No RSS
          • 🔳 Enable RSS
          • ☑️ Enable Interactive Search
          • ☑️ Enable Automatic Search
    • Download Clients (Optional)
      • Note: If you intend to do searches directly within Prowlarr, you need to add Download Clients. Otherwise, you do not need to add them here. For searches from your Apps, the download clients configured there are used instead.
      • Click + and select qBittorrent
        • Host: wireguard
        • Username/Password: Use qbittorrent credentials
      • Click + and select SABnzbd
        • Host: sabnzbd
        • Port: 8080 (even though the web interface is 8081)
        • Api Key: SABnzbd API key from its Config > General (Security) page
        • Default Category: you can add a prowlarr category in SABnzbd or use an existing category
    • General
      • Security
        • Authentication Required: Disabled for Local Addresses
  • Indexers
    • Add Indexer
      • Search for an indexer and click to configure it
      • ☑️ Enable
      • Torrent indexers: 1337x, LimeTorrents, TorrentDownload, YTS
      • Usenet indexers (require accounts): abNZB, altHUB, DrunkenSlug
        • Sync Profile: No RSS (prevents hitting indexer API limits)
        • API Key: Get the API key from your indexer's settings page
        • Query/Grab Limit: See this page for indexer query and grab limits

Bazarr

These custom settings are adapted from TRaSH Guide for qBittorrent.

Bazarr settings
  • Settings
    • Languages
      • Subtitles Language > Languages Filter
        • Add English to the list
      • Language Profiles
        • Add a new profile and choose the English (Normal or hearing impaired) language
        • You can also add English (Forced)
      • Default Settings
        • ☑️ Series
          • Profile: English profile name
        • ☑️ Movies
          • Profile: English profile name
    • Providers
      • Add: OpenSubtitles.com, Embedded Subtitles, subf2m.co
      • Note: Create an account on OpenSubtitles.com and input your login information
    • Sonarr
      • Address: sonarr
      • API Key: Sonarr API key from its Settings > General page
    • Radarr
      • Address: radarr
      • API Key: Radarr API key from its Settings > General page
    • Scheduler
      • Sonarr/Radarr Sync: 24 Hours (all boxes)
      • Disk Indexing: Manually (all boxes)
      • Search and Upgrade Subtitles: 24 Hours (all boxes)

Overseerr

Overseerr settings
  • Settings
    • Users > User Settings
      • ☑️ Enable New Plex Sign-In
      • Global Movie Request Limit: set if desired
      • Global Series Request Limit: set if desired
    • Plex
      • Plex Settings
        • Server: Choose local Plex server (if not done during initial setup)
        • Hostname/IP: Enter server IP (if not done during initial setup)
      • Plex Libraries
        • Enable Movies and TV Shows (if not done during initial setup)
      • Tautulli Settings (optional)
        • Hostname/IP: Enter server IP (if not done during initial setup)
        • API Key: Tautulli API key from its Settings > Web Interface page
    • Services
      • Radarr
        • ☑️ Default Server
        • Hostname/IP: radarr
        • API Key: Radarr API key from its Settings > General page
        • Set Quality Profile and Root Folder as desired
      • Sonarr
        • ☑️ Default Server
        • Hostname/IP: sonarr
        • API Key: Sonarr API key from its Settings > General page
        • Set Quality Profile and Root Folder as desired
    • Notifications
      • Configure desired notification agent. I used email to SMS.

Tautulli

Tautulli settings
  • Settings
    • Homepage > Watch Statistics
      • I unchecked a bunch of these to clean up the homepage
    • Plex Media Server
      • Plex IP Address: Choose local Plex server (192.168.x.y)
      • ☑️ Use secure connection
    • Notification Agents
      • I am using Webhook notification agents along with ntfy.sh. An example notification is shown below.
        • Add a new notification agent > Webhook
        • Webhook URL: https://ntfy.sh
        • Webhook Method: POST
        • Triggers: ☑️ Playback Start (or any other triggers you want)
        • Data: Playback Start > JSON Data
          {
            "topic": "nateflix-info",
            "icon": "{poster_url}",
            "title": "{server_name}",
            "message": "{user} started playing {title}{`str(' (S' + season_num00 + ':E' + episode_num00 + ')' if  media_type != 'movie' else '')`}\nDevice: {player}\nContainer: {container_decision!c} ({container!u}{`str(' → ' + stream_container if container_decision == 'transcode' else '' )`!u})\nVideo: {`str('Direct stream' if user_direct_streams > 0 else 'Direct play' if user_direct_plays > 0 else video_decision)`!c} ({video_codec!u}{`str(' → ' + stream_video_codec if video_decision == 'transcode' else '')`!u} {stream_video_full_resolution})\nAudio: {`str('Direct stream' if user_direct_streams > 0 else 'Direct play' if user_direct_plays > 0 else audio_decision)`!c} ({audio_codec!u}{`str(' → ' + stream_audio_codec if audio_decision == 'transcode' else '' )`!u} {stream_audio_channel_layout})\nBitrate: {`round(float(stream_bandwidth) / 1000, 1)`> Mbps}"
          }
        • Note: notify_text_eval = 1 must be manually enabled in the Tautulli config.ini file to enable expressions.
    • Changing the newsletter header
      • Edit ${CONFIG_HOME}/tautulli/templates/recently_added.html
      • Search for newsletter-header.png
      • Replace the line with your own image: <img src="YOUR-IMAGE-URL-OR-PATH">

Monitoring

A monitoring stack is included using Prometheus/Grafana with data collected via cAdvisor and node-exporter. The monitoring services are deployed by including the monitoring/docker-compose.yml from the main docker-compose.yml file.

Service Description
prometheus Prometheus Monitoring system and time series database
grafana Grafana Analytics and interactive visualization web application
cAdvisor cAdvisor Provides resource usage and performance characteristics of running containers
node-exporter Node exporter Exposes a wide variety of hardware- and kernel-related metrics

The Grafana dashboard is accessible at http://<server-ip>:3000.

grafana

Remote access

Remote access to the server from the internet can be configured using dynamic DNS and a reverse proxy.

Service Description
duckdns DuckDNS Static IP management
grafana Nginx Proxy Manager Forwards traffic to your services running at home, including free SSL

DuckDNS

The DuckDNS service automatically keeps your DuckDNS account in sync with your server's public IP address.

Create a DuckDNS account

  1. Create a free DuckDNS account
  2. Create a unique subdomain that will be used as an alias to your server's public IP address
  3. Copy the Token from the DuckDNS account homepage
  4. Write the token to a file that will be handled by Docker Secrets:
    printf "YOUR-TOKEN" > secrets/duckdns_token.txt

Nginx Proxy Manager

The Ngnix Proxy Manager service allows you to manage subdomains for external access to your services, as well as manage access lists to the subdomains.

The Nginx Proxy Manager dashboard is accessible at http://<server-ip>:81

Create an access list

  1. On the Access Lists screen, click Add Access List
  2. In the "Detail" tab, set a name and enable Pass Auth to Host
  3. In the "Authorization" tab, set at least one username/password that will be used to access Proxy Hosts
  4. In the "Access" tab, set allow to all
  5. Click Save
  6. You can create multiple access lists, for example, one for admin only, and one that includes other users

Create proxy host(s)

DuckDNS will route any wildcard "sub-sub-domain" (i.e. service1.my-alias.duckdns.org) to your server, so Nginx Proxy Manager can then handle routing sub-sub-domain requests to your individual services within your network. If this seems confusing, this diagram may help:

flowchart LR
    classDef redbox fill:#ff0063
    C(Client) -->|Request\nservice1.my-alias.duckdns.org| D(DuckDNS)
    D -->|Request| N[Nginx Proxy\n Manager]
    subgraph Home Server
        N --> S1[Service1]:::redbox
        N --> S2[Service2]
        N --> S3[Service3]
    end
Loading
  1. On the Proxy Hosts screen, click Add Proxy Host
  2. In the Domain Names box, create a subdomain for your service using the DuckDNS alias for your server. Example: radarr.my-alias.duckdns.org.
  3. Set the IP of your server and exposed port of your service
  4. Enable Block Common Exploits
  5. Set the Access List to the one created above, or else your service will publicly available (relies only on the service login, if any, for security)
  6. In the "SSL" tab, select "Request a new SSL Certificate".
  7. Enable "Force SSL" and create a new certificate

nginx-host

Router port fowarding

In order to reach the server from the internet, configure your router to forward ports 80 (HTTP) and 443 (HTTPS) to the server/ports where Nginx Proxy Manager service is listening. See the Nginx Proxy Manager documentation for more details.

port-forward

You can now test if external access is working by visiting one of your subdomains from outside the local network.

Helpful commands

Starting and stopping services:

# Start services in the background (all by default, can specify individual service(s))
docker compose up [service-names] -d

# Stop services (all by default, can specify individual service(s))
docker compose down [service-names]

View logs for services:

docker compose logs <service-name>

Restart services and recreate volumes with recent updates:

docker-compose up --renew-anon-volumes|-V

Update Docker images to their latest versions

docker compose down
docker compose pull
docker compose up -d
docker image prune

Troubleshooting

qBittorrent: Can't access web UI from the netowrk

I was able to restore access by running this command after starting services:

sudo iptables -t mangle -F

Additional notes

Mounting an external USB disk on host and Docker containers

In this example, I have an NTFS-formatted external USB hard drive connected to my host PC.

  1. On the host PC, create a directory for the mount point.
sudo mkdir /mnt/usb
  1. Get the UUID of the USB drive. Look for an entry that matches your drive.
sudo blkid

# example output
# ...
# /dev/sdb1: LABEL="MY_DRIVE" BLOCK_SIZE="512" UUID="ABCD1234" TYPE="ntfs" PARTUUID="00075ff3-01"
# ...
  1. Edit the fstab file to automount the USB drive on boot. Use the UUID, mount point, and filesystem type found in the previous steps.
sudo vim /etc/fstab
# for NTFS
UUID=ABCD1234 /mnt/usb ntfs defaults,noatime,nofail,umask=000 0 2
# for ext4
UUID=ABCD1234 /mnt/usb2 ext4 defaults,noatime,nofail 0 2
  1. Mount the drive and ensure it is available at the mount point.
sudo mount -a

ls /mnt/usb
  1. Set up download paths on the drive to be used by the services.
# set permissions if necessary
sudo chown -Rv ${USER}:${USER} /mnt/usb

# create destination directories on the drive
DATA_DIR=/mnt/usb
sudo mkdir -pv ${DATA_DIR}/{torrents,usenet,media}/{tv,movies}
sudo chmod -Rv 775 ${DATA_DIR}/
sudo chown -Rv ${USER}:${USER} ${DATA_DIR}/

If you are switching an internal drive to external storage for all media, you can stop here. If you want to mount the external drive as additional mount in the container (i.e. /usb, continue with the steps below to update service settings.

qBittorrent

  • Settings > Downloads > Default Save Path: /usb/torrents
  • Categories > movies > Save Path: /usb/torrents/movies
  • Categories > tv > Save Path: /usb/torrents/tv

Note: if using VueTorrent UI, you might have to switch back to the default UI to update the category paths

SABnzbd

  • Config > Folders > Temporary Download Folder: /usb/usenet/incomplete
  • Config > Folders > Completed Download Folder: /usb/usenet
  • Click Save Changes

Plex

  • Settings > Manage > Libraries > Movies > Edit Library > Add Folders: /usb/media/movies
  • Settings > Manage > Libraries > TV Shows > Edit Library > Add Folders: /usb/media/tv

Note: you can remove any existing paths that are no longer needed

  • Home > Movies ... > Scan Library Files to confirm changes took effect
  • Home > TV Shows ... > Scan Library Files to confirm changes took effect

Radarr

  • Settings > Media Management > Root Folders: Add /usb/media/movies
  • Settings > Lists > PlexImport > Root Folder: /usb/media/movies
  • Movies > Edit Movies > Select All > Edit: Change Root Folder to /usb/media/movies, Apply, Allow moving files

Note: you can remove any existing paths that are no longer needed

Sonarr

  • Settings > Media Management > Root Folders: Add /usb/media/tv
  • Settings > Import Lists > PlexImport > Root Folder: /usb/media/tv
  • Series > Mass Editor > Select All: Change Root Folder to /usb/media/tv, Allow moving files

Note: you can remove any existing paths that are no longer needed

Enable USB hard drive idle mode

Some USB hard drives do not spin down after a period of time. This section describes how to enable the functionality manually.

  1. Download and install the hd-idle utility.
curl -L https://github.com/adelolmo/hd-idle/releases/download/v1.20/hd-idle_1.20_amd64.deb -o hd-idle.deb
sudo dpkg -i hd-idle.deb
  1. Configure hd-idle to set the idle timeout for your USB drive (in seconds). The UUID is found via sudo blkid.
sudo vim /etc/default/hd-idle
# Uncomment and update this line to enable the utility
START_HD_IDLE=true

# Uncomment and update this line to configure the idle timeout value
HD_IDLE_OPTS="-i 0 -a /dev/disk/by-uuid/<DRIVE-UUID> -i 600 -c ata -l /var/log/hd-idle.log"
  1. Start the hd-idle service.
systemctl unmask hd-idle.service
systemctl start hd-idle
systemctl enable hd-idle
  1. Read the logs
sudo vim /var/log/hd-idle.log

Mounting a network share on host and Docker containers

Note: This will result in non-optimal copying/moving due to hardlinks not functioning across filesystems/shares. For this reason, I am using a USB hard drive directly connected to the host PC rather than this method, but leaving these steps here for reference.

This example assumes a USB hard drive is connected to the router's USB port, and is shared as an SMB share on the network.

  1. On the host, install cifs and create the directory to be used as the mount point
sudo apt install cifs-utils -y
sudo mkdir /mnt/smb-share # directory that will act as the mount point for the SMB share
  1. Edit the fstab file to automount the network share on boot (and after network is up)
sudo vim /etc/fstab
# <SHARE-IP>/<DIRECTORY>  <MOUNT-POINT>   <TYPE> <OPTIONS>                <BACKUP> <FSCK>
//192.168.29.1/usb-drive  /mnt/smb-share  cifs   _netdevuid=nate,vers=1.0 0        0
  1. In Plex, add the shared directory(s) to your library. In this example, I mapped the shared directory to /smb-share in the container.
plex:
  volumes:
    - /mnt/smb-share:/smb-share

Disable laptop suspend when lid is closed

When using a laptop as a server, the system may suspend when the lid is closed. To prevent this, make the following modification and restart the computer.

sudo vim /etc/systemd/logind.conf

Uncomment and edit the following lines:

HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore

Restart host on a schedule

I set the host PC to restart every Sunday at 4 AM with a Cron job

sudo crontab -e

# add the following line, save, and quit the editor
0 4 * * SUN /sbin/reboot

# verify the rule was saved
sudo crontab -l

Disable Wi-Fi radio

Find the wlan adapter name, then disable the radio:

sudo lshw -C network

# example output:
# logical name: <wireless adapter name>

sudo apt install net-tools
sudo ifconfig <wireless adapter name> down

Useful packages

LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | grep -Po '"tag_name": "v\K[^"]*')
curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
tar xf lazygit.tar.gz lazygit
sudo install lazygit /usr/local/bin

sudo apt install -y
  avahi-daemon
  unzip
  ripgrip
  fd-find
  net-tools
  tree

curl -L https://bit.ly/glances | /bin/bash

SSH without password

Configure SSH keys to connect to the server without a password.

Create a key pair on the client machine. Press <Enter> at all prompts:

ssh-keygen

You now have a public and private key that you can use to authenticate. The next step is to place the public key on your server so that you can use SSH-key-based authentication to log in:

ssh-copy-id username@remote_host

Type yes and enter your password when prompted.

Mirror a drive with rsync

This can be useful for making clones/backups or moving to a new drive.

# --archive, -a            archive mode is -rlptgoD (no -A,-X,-U,-N,-H)
# --human-readable, -h     output numbers in a human-readable format
# --verbose, -v            increase verbosity
# --one-file-system, -x    don't cross filesystem boundaries
# --acls, -A               preserve ACLs (implies --perms)
# --hard-links, -H         preserve hard links
# --whole-file, -W         copy files whole (w/o delta-xfer algorithm)
# --xattrs, -X             preserve extended attributes

rsync -ahvxAHWX --progress [--dry-run] /mnt/usb/ /mnt/usb2/