Compare commits
17 Commits
b8079666bd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cf78372b71 | |||
| 8c5cdb111d | |||
| 42ee02d8e7 | |||
| 83016e268c | |||
| 3539cc3d3e | |||
| 4db5c27755 | |||
| 8051ea33e3 | |||
| cc51dd386c | |||
| 3ce7262e38 | |||
| a0fb667642 | |||
| bccf28da84 | |||
| 3f05975393 | |||
| 44cdc60ab3 | |||
| dc54b33281 | |||
| 6c1e6b5700 | |||
| 7162827ab3 | |||
| fba2c07e2c |
106
.env.example
106
.env.example
@@ -1,32 +1,82 @@
|
||||
# --- Docker Compose Settings ---
|
||||
# Comma-separated list of optional service profiles to enable (e.g., lidarr,sabnzbd,adguardhome)
|
||||
COMPOSE_PROFILES=
|
||||
# Path separator for COMPOSE_FILE (use ';' for Windows)
|
||||
COMPOSE_PATH_SEPARATOR=:
|
||||
# Colon-separated list of compose files to use. Allows extending the base configuration.
|
||||
COMPOSE_FILE=docker-compose.yml:adguardhome/docker-compose.yml:tandoor/docker-compose.yml:joplin/docker-compose.yml:homeassistant/docker-compose.yml:immich/docker-compose.yml
|
||||
|
||||
# --- Core System Settings ---
|
||||
# Linux User ID. Find yours with `id -u`. Crucial for file permissions.
|
||||
USER_ID=1000
|
||||
# Linux Group ID. Find yours with `id -g`. Crucial for file permissions.
|
||||
GROUP_ID=1000
|
||||
# Your local timezone (e.g., America/New_York, Europe/London, Asia/Manila). See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
TIMEZONE="America/New_York"
|
||||
|
||||
# --- Host Paths ---
|
||||
# Base directory on host for storing service configuration files. '.' stores them in subdirectories within the project folder.
|
||||
CONFIG_ROOT="."
|
||||
# Main directory on host containing media libraries (movies, TV, music, books).
|
||||
DATA_ROOT="/mnt/data"
|
||||
# Directory on host for download clients (qBittorrent/SABnzbd). Should be on the same filesystem as DATA_ROOT for hardlinks.
|
||||
DOWNLOAD_ROOT="/mnt/data/torrents"
|
||||
# Upload location for Immich (if profile enabled)
|
||||
IMMICH_UPLOAD_LOCATION="/mnt/data/photos"
|
||||
PIA_LOCATION=ca
|
||||
PIA_USER=
|
||||
PIA_PASS=
|
||||
PIA_LOCAL_NETWORK="192.168.0.0/16"
|
||||
HOSTNAME=localhost
|
||||
HOMEASSISTANT_HOSTNAME=
|
||||
IMMICH_HOSTNAME=
|
||||
ADGUARD_HOSTNAME=
|
||||
ADGUARD_USERNAME=
|
||||
ADGUARD_PASSWORD=
|
||||
|
||||
# --- Tailscale Settings ---
|
||||
# Required. Auth key from Tailscale Admin Console (Settings > Keys). Use a reusable or ephemeral key.
|
||||
TAILSCALE_AUTHKEY=
|
||||
# Desired hostname for this NAS within your Tailscale network.
|
||||
TAILSCALE_HOSTNAME=tailscale-nas
|
||||
# Required. Your Tailnet domain (e.g., your-tailnet-name.ts.net).
|
||||
TAILSCALE_TAILNET_DOMAIN=your-tailnet.ts.net
|
||||
# Optional tags to apply to the Tailscale node (e.g., tag:nas).
|
||||
TAILSCALE_TAGS=tag:nas
|
||||
# Enable Tailscale Funnel (public access) for HTTPS? Set to 'true' or 'false'. 'false' uses Serve (Tailnet only, recommended).
|
||||
ENABLE_FUNNEL_HTTPS=false
|
||||
|
||||
# --- Primary Hostname ---
|
||||
# Primary hostname used by Traefik for routing. Derived from Tailscale settings by default.
|
||||
HOSTNAME=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
|
||||
|
||||
# --- Application Credentials ---
|
||||
# qBittorrent Web UI Credentials (change default!)
|
||||
QBITTORRENT_USERNAME=admin
|
||||
QBITTORRENT_PASSWORD=adminadmin
|
||||
DNS_CHALLENGE=true
|
||||
DNS_CHALLENGE_PROVIDER=cloudflare
|
||||
LETS_ENCRYPT_CA_SERVER="https://acme-v02.api.letsencrypt.org/directory"
|
||||
LETS_ENCRYPT_EMAIL=
|
||||
CLOUDFLARE_EMAIL=
|
||||
CLOUDFLARE_DNS_API_TOKEN=
|
||||
CLOUDFLARE_ZONE_API_TOKEN=
|
||||
# Calibre-Web Credentials (if profile enabled)
|
||||
CALIBRE_USERNAME=admin
|
||||
CALIBRE_PASSWORD=admin123
|
||||
# Immich Database Password (if profile enabled)
|
||||
IMMICH_DB_PASSWORD=postgres
|
||||
|
||||
# --- Homepage Settings ---
|
||||
HOMEPAGE_VAR_TITLE="Docker-Compose NAS"
|
||||
HOMEPAGE_VAR_SEARCH_PROVIDER=google
|
||||
HOMEPAGE_VAR_HEADER_STYLE=boxed
|
||||
# Weather Widget (Optional)
|
||||
HOMEPAGE_VAR_WEATHER_CITY=
|
||||
HOMEPAGE_VAR_WEATHER_LAT=
|
||||
HOMEPAGE_VAR_WEATHER_LONG=
|
||||
HOMEPAGE_VAR_WEATHER_UNIT=metric
|
||||
|
||||
# --- Authelia Settings ---
|
||||
# Generate strong random secrets for these using tools like `openssl rand -hex 32`
|
||||
AUTHELIA_JWT_SECRET= # Example: your_strong_jwt_secret
|
||||
AUTHELIA_SESSION_SECRET= # Example: your_strong_session_secret
|
||||
AUTHELIA_STORAGE_ENCRYPTION_KEY= # Example: your_strong_storage_encryption_key
|
||||
AUTHELIA_REDIS_PASSWORD= # Example: your_strong_redis_password
|
||||
|
||||
# Google OIDC Provider Settings (Get from Google Cloud Console - https://console.cloud.google.com/apis/credentials)
|
||||
AUTHELIA_GOOGLE_OIDC_CLIENT_ID= # Example: your-google-client-id.apps.googleusercontent.com
|
||||
AUTHELIA_GOOGLE_OIDC_CLIENT_SECRET= # Example: GOCSPX-your-google-client-secret
|
||||
|
||||
# Authelia Session Configuration
|
||||
AUTHELIA_SESSION_DOMAIN=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
|
||||
AUTHELIA_DEFAULT_REDIRECT_URL=https://${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/home
|
||||
|
||||
# --- API Keys & Integration Tokens (Optional - Mainly for Homepage Widgets) ---
|
||||
# Find API keys within each application's settings (usually Settings > General or Security)
|
||||
SONARR_API_KEY=
|
||||
RADARR_API_KEY=
|
||||
LIDARR_API_KEY=
|
||||
@@ -37,16 +87,12 @@ JELLYSEERR_API_KEY=
|
||||
SABNZBD_API_KEY=
|
||||
IMMICH_API_KEY=
|
||||
HOMEASSISTANT_ACCESS_TOKEN=
|
||||
HOMEPAGE_VAR_TITLE="Docker-Compose NAS"
|
||||
HOMEPAGE_VAR_SEARCH_PROVIDER=google
|
||||
HOMEPAGE_VAR_HEADER_STYLE=boxed
|
||||
HOMEPAGE_VAR_WEATHER_CITY=
|
||||
HOMEPAGE_VAR_WEATHER_LAT=
|
||||
HOMEPAGE_VAR_WEATHER_LONG=
|
||||
HOMEPAGE_VAR_WEATHER_UNIT=metric
|
||||
IMMICH_DB_PASSWORD=postgres
|
||||
CALIBRE_USERNAME=admin
|
||||
CALIBRE_PASSWORD=admin123
|
||||
# AdGuard Home Credentials (if profile enabled)
|
||||
ADGUARD_USERNAME=
|
||||
ADGUARD_PASSWORD=
|
||||
|
||||
# --- Optional Service Settings ---
|
||||
# Decluttarr Settings (if profile enabled)
|
||||
DECLUTTARR_TEST_RUN=True
|
||||
DECLUTTARR_REMOVE_TIMER=60
|
||||
DECLUTTARR_REMOVE_FAILED=True
|
||||
@@ -54,3 +100,9 @@ DECLUTTARR_REMOVE_FAILED_IMPORTS=True
|
||||
DECLUTTARR_REMOVE_METADATA_MISSING=True
|
||||
DECLUTTARR_REMOVE_MISSING_FILES=True
|
||||
DECLUTTARR_REMOVE_ORPHANS=True
|
||||
|
||||
# --- Other Hostnames (Optional Services) ---
|
||||
# Set these if you need specific hostnames for these services (e.g., for Home Assistant integrations)
|
||||
HOMEASSISTANT_HOSTNAME=
|
||||
IMMICH_HOSTNAME=
|
||||
ADGUARD_HOSTNAME=
|
||||
|
||||
724
README.md
724
README.md
@@ -1,586 +1,296 @@
|
||||
# Docker Compose NAS
|
||||
|
||||
After searching for the perfect NAS solution, I realized what I wanted could be achieved
|
||||
with some Docker containers on a vanilla Linux box. The result is an opinionated Docker Compose configuration capable of
|
||||
browsing indexers to retrieve media resources and downloading them through a WireGuard VPN with port forwarding.
|
||||
SSL certificates and remote access through Tailscale are supported.
|
||||
This project provides a comprehensive, self-hosted media and utility server setup using Docker Compose. It aims to replicate and enhance the functionality of a typical NAS using containerized applications on a standard Linux host.
|
||||
|
||||
Requirements: Any Docker-capable recent Linux box with Docker Engine and Docker Compose V2.
|
||||
I am running it in Ubuntu Server 22.04; I also tested this setup on a [Synology DS220+ with DSM 7.1](#synology-quirks).
|
||||
The core idea is to manage media libraries (movies, TV shows, music), automate downloads securely, provide easy access via a dashboard, and enable remote access through Tailscale.
|
||||
|
||||

|
||||
## Features
|
||||
|
||||
## Table of Contents
|
||||
This stack includes:
|
||||
|
||||
<!-- TOC -->
|
||||
* [Docker Compose NAS](#docker-compose-nas)
|
||||
* [Table of Contents](#table-of-contents)
|
||||
* [Applications](#applications)
|
||||
* [Quick Start](#quick-start)
|
||||
* [Environment Variables](#environment-variables)
|
||||
* [PIA WireGuard VPN](#pia-wireguard-vpn)
|
||||
* [Sonarr, Radarr & Lidarr](#sonarr-radarr--lidarr)
|
||||
* [File Structure](#file-structure)
|
||||
* [Download Client](#download-client)
|
||||
* [Prowlarr](#prowlarr)
|
||||
* [qBittorrent](#qbittorrent)
|
||||
* [Jellyfin](#jellyfin)
|
||||
* [Homepage](#homepage)
|
||||
* [Jellyseerr](#jellyseerr)
|
||||
* [Traefik and SSL Certificates](#traefik-and-ssl-certificates)
|
||||
* [Accessing from the outside with Tailscale](#accessing-from-the-outside-with-tailscale)
|
||||
* [Optional Services](#optional-services)
|
||||
* [FlareSolverr](#flaresolverr)
|
||||
* [SABnzbd](#sabnzbd)
|
||||
* [AdGuard Home](#adguard-home)
|
||||
* [Encryption](#encryption)
|
||||
* [DHCP](#dhcp)
|
||||
* [Expose DNS Server with Tailscale](#expose-dns-server-with-tailscale)
|
||||
* [Calibre-Web](#calibre-web)
|
||||
* [Decluttarr](#decluttarr)
|
||||
* [Tandoor](#tandoor)
|
||||
* [Joplin](#joplin)
|
||||
* [Home Assistant](#home-assistant)
|
||||
* [Immich](#immich)
|
||||
* [Customization](#customization)
|
||||
* [Optional: Using the VPN for *arr apps](#optional-using-the-vpn-for-arr-apps)
|
||||
* [Synology Quirks](#synology-quirks)
|
||||
* [Free Ports 80 and 443](#free-ports-80-and-443)
|
||||
* [Install Synology WireGuard](#install-synology-wireguard)
|
||||
* [Free Port 1900](#free-port-1900)
|
||||
* [User Permissions](#user-permissions)
|
||||
* [Synology DHCP Server and Adguard Home Port Conflict](#synology-dhcp-server-and-adguard-home-port-conflict)
|
||||
* [Use Separate Paths for Torrents and Storage](#use-separate-paths-for-torrents-and-storage)
|
||||
* [NFS Share](#nfs-share)
|
||||
* [Static IP](#static-ip)
|
||||
* [Laptop Specific Configuration](#laptop-specific-configuration)
|
||||
<!-- TOC -->
|
||||
* **Reverse Proxy & Service Discovery:** [Traefik](https://traefik.io) automatically routes traffic to services.
|
||||
* **Media Management:**
|
||||
* [Sonarr](https://sonarr.tv): TV show management.
|
||||
* [Radarr](https://radarr.video): Movie management.
|
||||
* [Lidarr](https://lidarr.audio) (Optional): Music management.
|
||||
* [Bazarr](https://www.bazarr.media/): Subtitle management.
|
||||
* **Indexers & Downloads:**
|
||||
* [Prowlarr](https://github.com/Prowlarr/Prowlarr): Indexer management for *arr apps.
|
||||
* [qBittorrent](https://www.qbittorrent.org): Bittorrent client (can be configured to run through a VPN).
|
||||
* [SABnzbd](https://sabnzbd.org/) (Optional): Usenet download client.
|
||||
* **Media Server:** [Jellyfin](https://jellyfin.org) organizes and streams your media.
|
||||
* **Request Management:** [Jellyseerr](https://github.com/FallenBagel/jellyseerr) allows users (including Jellyfin users) to request media.
|
||||
* **Dashboard:** [Homepage](https://gethomepage.dev) provides a central dashboard to access all services.
|
||||
* **Remote Access:** [Tailscale](https://tailscale.com) provides secure access to your services from anywhere without opening firewall ports. It handles HTTPS termination.
|
||||
* **Utilities:**
|
||||
* [Watchtower](https://containrrr.dev/watchtower/): Automatically updates running containers to the latest image.
|
||||
* [Autoheal](https://github.com/willfarrell/docker-autoheal/): Monitors and restarts unhealthy containers.
|
||||
* [Unpackerr](https://unpackerr.zip): Automatically extracts downloaded archives.
|
||||
* **Other Optional Services:** AdGuard Home, Calibre-Web, Decluttarr, Tandoor Recipes, Joplin Server, Home Assistant, Immich Photos (enable via profiles).
|
||||
|
||||
## Applications
|
||||
## Prerequisites
|
||||
|
||||
| **Application** | **Description** | **Image** | **URL** |
|
||||
|--------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|--------------|
|
||||
| [Sonarr](https://sonarr.tv) | PVR for newsgroup and bittorrent users | [linuxserver/sonarr](https://hub.docker.com/r/linuxserver/sonarr) | /sonarr |
|
||||
| [Radarr](https://radarr.video) | Movie collection manager for Usenet and BitTorrent users | [linuxserver/radarr](https://hub.docker.com/r/linuxserver/radarr) | /radarr |
|
||||
| [Bazarr](https://www.bazarr.media/) | Companion application to Sonarr and Radarr that manages and downloads subtitles | [linuxserver/bazarr](https://hub.docker.com/r/linuxserver/bazarr) | /bazarr |
|
||||
| [Prowlarr](https://github.com/Prowlarr/Prowlarr) | Indexer aggregator for Sonarr and Radarr | [linuxserver/prowlarr:latest](https://hub.docker.com/r/linuxserver/prowlarr) | /prowlarr |
|
||||
| [PIA WireGuard VPN](https://github.com/thrnz/docker-wireguard-pia) | Encapsulate qBittorrent traffic in [PIA](https://www.privateinternetaccess.com/) using [WireGuard](https://www.wireguard.com/) with port forwarding. | [thrnz/docker-wireguard-pia](https://hub.docker.com/r/thrnz/docker-wireguard-pia) | |
|
||||
| [qBittorrent](https://www.qbittorrent.org) | Bittorrent client with a complete web UI<br/>Uses VPN network<br/>Using Libtorrent 1.x | [linuxserver/qbittorrent:libtorrentv1](https://hub.docker.com/r/linuxserver/qbittorrent) | /qbittorrent |
|
||||
| [Unpackerr](https://unpackerr.zip) | Automated Archive Extractions | [golift/unpackerr](https://hub.docker.com/r/golift/unpackerr) | |
|
||||
| [Jellyfin](https://jellyfin.org) | Media server designed to organize, manage, and share digital media files to networked devices | [linuxserver/jellyfin](https://hub.docker.com/r/linuxserver/jellyfin) | /jellyfin |
|
||||
| [Jellyseer](https://jellyfin.org) | Manages requests for your media library | [fallenbagel/jellyseerr](https://hub.docker.com/r/fallenbagel/jellyseerr) | /jellyseer |
|
||||
| [Homepage](https://gethomepage.dev) | Application dashboard | [gethomepage/homepage](https://github.com/gethomepage/homepage/pkgs/container/homepage) | / |
|
||||
| [Traefik](https://traefik.io) | Reverse proxy | [traefik](https://hub.docker.com/_/traefik) | |
|
||||
| [Watchtower](https://containrrr.dev/watchtower/) | Automated Docker images update | [containrrr/watchtower](https://hub.docker.com/r/containrrr/watchtower) | |
|
||||
| [Autoheal](https://github.com/willfarrell/docker-autoheal/) | Monitor and restart unhealthy Docker containers | [willfarrell/autoheal](https://hub.docker.com/r/willfarrell/autoheal) | |
|
||||
| [Lidarr](https://lidarr.audio) | Optional - Music collection manager for Usenet and BitTorrent users<br/>Enable with `COMPOSE_PROFILES=lidarr` | [linuxserver/lidarr](https://hub.docker.com/r/linuxserver/lidarr) | /lidarr |
|
||||
| [SABnzbd](https://sabnzbd.org/) | Optional - Free and easy binary newsreader<br/>Enable with `COMPOSE_PROFILES=sabnzbd` | [linuxserver/sabnzbd](https://hub.docker.com/r/linuxserver/sabnzbd) | /sabnzbd |
|
||||
| [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) | Optional - Proxy server to bypass Cloudflare protection in Prowlarr<br/>Enable with `COMPOSE_PROFILES=flaresolverr` | [flaresolverr/flaresolverr](https://hub.docker.com/r/flaresolverr/flaresolverr) | |
|
||||
| [AdGuard Home](https://adguard.com/en/adguard-home/overview.html) | Optional - Network-wide software for blocking ads & tracking<br/>Enable with `COMPOSE_PROFILES=adguardhome` | [adguard/adguardhome](https://hub.docker.com/r/adguard/adguardhome) | |
|
||||
| [Tandoor](https://tandoor.dev) | Optional - Smart recipe management<br/>Enable with `COMPOSE_PROFILES=tandoor` | [vabene1111/recipes](https://hub.docker.com/r/vabene1111/recipes) | /recipes |
|
||||
| [Joplin](https://joplinapp.org) | Optional - Note taking application<br/>Enable with `COMPOSE_PROFILES=joplin` | [joplin/server](https://hub.docker.com/r/joplin/server) | /joplin |
|
||||
| [Home Assistant](https://www.home-assistant.io) | Optional - Open source home automation that puts local control and privacy first<br/>Enable with `COMPOSE_PROFILES=homeassistant` | [home-assistant/home-assistant:stable](https://ghcr.io/home-assistant/home-assistant) | |
|
||||
| [Immich](https://immich.app) | Optional - Self-hosted photo and video management solution<br/>Enable with `COMPOSE_PROFILES=immich` | [immich-app/immich-server:release](https://ghcr.io/immich-app/immich-server) | |
|
||||
| [Calibre-Web](https://github.com/janeczku/calibre-web) | Optional - Web app for browsing, reading and downloading eBooks stored in a Calibre database<br/>Enable with `COMPOSE_PROFILES=calibre-web` | [linuxserver/calibre-web](https://hub.docker.com/r/linuxserver/calibre-web) | /calibre |
|
||||
| [Decluttarr](https://github.com/ManiMatter/decluttarr) | Optional - Keeps the download queues free of stalled and redundant downloads. <br/>Enable with `COMPOSE_PROFILES=decluttarr` | [manimatter/decluttarr:latest](https://ghcr.io/manimatter/decluttarr:latest) | |
|
||||
|
||||
Optional containers are not enabled by default, they need to be enabled,
|
||||
see [Optional Services](#optional-services) for more information.
|
||||
* **Linux Host:** Any recent Linux distribution capable of running Docker. Tested on Ubuntu Server 22.04.
|
||||
* **Docker Engine:** Install the latest version of Docker Engine. [Official Installation Guide](https://docs.docker.com/engine/install/).
|
||||
* **Docker Compose V2:** Ensure you have Docker Compose V2 (usually installed as a Docker plugin, invoked via `docker compose`). [Official Installation Guide](https://docs.docker.com/compose/install/).
|
||||
* **User Permissions:** You'll need a user account that can run `docker` commands (usually by adding the user to the `docker` group) or run `docker compose` via `sudo`.
|
||||
* **SELinux (If Enabled):** If your host uses SELinux (e.g., Fedora, CentOS, RHEL), you might need additional host configuration. See the [Troubleshooting](#selinux-socket-permissions) section.
|
||||
|
||||
## Quick Start
|
||||
|
||||
`cp .env.example .env`, edit to your needs then `docker compose up -d`.
|
||||
1. **Clone the Repository:**
|
||||
```bash
|
||||
git clone https://github.com/AdrienPoupa/docker-compose-nas.git
|
||||
cd docker-compose-nas
|
||||
```
|
||||
|
||||
For the first time, run `./update-config.sh` to update the applications base URLs and set the API keys in `.env`.
|
||||
2. **Create Configuration File:**
|
||||
Copy the example environment file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
If you want to show Jellyfin information in the homepage, create it in Jellyfin settings and fill `JELLYFIN_API_KEY`.
|
||||
3. **Edit `.env` File:**
|
||||
Open the `.env` file with a text editor and configure it according to your system and preferences. **This is the most crucial step.** See the detailed [Configuration (`.env` File)](#configuration-env-file) section below for explanations of each variable. Minimally, you **must** set `USER_ID`, `GROUP_ID`, `TIMEZONE`, `HOSTNAME`, and `TAILSCALE_AUTHKEY`.
|
||||
|
||||
## Environment Variables
|
||||
4. **Start the Stack:**
|
||||
Run Docker Compose (use `sudo` if your user isn't in the `docker` group):
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
This will pull the necessary images and start all the core services in the background.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------|
|
||||
| `COMPOSE_FILE` | Docker compose files to load | |
|
||||
| `COMPOSE_PROFILES` | Docker compose profiles to load (`flaresolverr`, `adguardhome`, `sabnzbd`) | |
|
||||
| `USER_ID` | ID of the user to use in Docker containers | `1000` |
|
||||
| `GROUP_ID` | ID of the user group to use in Docker containers | `1000` |
|
||||
| `TIMEZONE` | TimeZone used by the container. | `America/New_York` |
|
||||
| `CONFIG_ROOT` | Host location for configuration files | `.` |
|
||||
| `DATA_ROOT` | Host location of the data files | `/mnt/data` |
|
||||
| `DOWNLOAD_ROOT` | Host download location for qBittorrent, should be a subfolder of `DATA_ROOT` | `/mnt/data/torrents` |
|
||||
| `PIA_LOCATION` | Servers to use for PIA. [see list here](https://serverlist.piaservers.net/vpninfo/servers/v6) | `ca` (Montreal, Canada) |
|
||||
| `PIA_USER` | PIA username | |
|
||||
| `PIA_PASS` | PIA password | |
|
||||
| `PIA_LOCAL_NETWORK` | PIA local network | `192.168.0.0/16` |
|
||||
| `HOSTNAME` | Hostname of the NAS, could be a local IP or a domain name | `localhost` |
|
||||
| `ADGUARD_HOSTNAME` | Optional - AdGuard Home hostname used, if enabled | |
|
||||
| `ADGUARD_USERNAME` | Optional - AdGuard Home username to show details in the homepage, if enabled | |
|
||||
| `ADGUARD_PASSWORD` | Optional - AdGuard Home password to show details in the homepage, if enabled | |
|
||||
| `QBITTORRENT_USERNAME` | qBittorrent username to access the web UI | `admin` |
|
||||
| `QBITTORRENT_PASSWORD` | qBittorrent password to access the web UI | `adminadmin` |
|
||||
| `DNS_CHALLENGE` | Enable/Disable DNS01 challenge, set to `false` to disable. | `true` |
|
||||
| `DNS_CHALLENGE_PROVIDER` | Provider for DNS01 challenge, [see list here](https://doc.traefik.io/traefik/https/acme/#providers). | `cloudflare` |
|
||||
| `LETS_ENCRYPT_CA_SERVER` | Let's Encrypt CA Server used to generate certificates, set to production by default.<br/>Set to `https://acme-staging-v02.api.letsencrypt.org/directory` to test your changes with the staging server. | `https://acme-v02.api.letsencrypt.org/directory` |
|
||||
| `LETS_ENCRYPT_EMAIL` | E-mail address used to send expiration notifications | |
|
||||
| `CLOUDFLARE_EMAIL` | CloudFlare Account email | |
|
||||
| `CLOUDFLARE_DNS_API_TOKEN` | API token with `DNS:Edit` permission | |
|
||||
| `CLOUDFLARE_ZONE_API_TOKEN` | API token with `Zone:Read` permission | |
|
||||
| `SONARR_API_KEY` | Sonarr API key to show information in the homepage | |
|
||||
| `RADARR_API_KEY` | Radarr API key to show information in the homepage | |
|
||||
| `LIDARR_API_KEY` | Lidarr API key to show information in the homepage | |
|
||||
| `PROWLARR_API_KEY` | Prowlarr API key to show information in the homepage | |
|
||||
| `BAZARR_API_KEY` | Bazarr API key to show information in the homepage | |
|
||||
| `JELLYFIN_API_KEY` | Jellyfin API key to show information in the homepage | |
|
||||
| `JELLYSEERR_API_KEY` | Jellyseer API key to show information in the homepage | |
|
||||
| `SABNZBD_API_KEY` | Sabnzbd API key to show information in the homepage | |
|
||||
| `HOMEPAGE_VAR_TITLE` | Title of the homepage | `Docker-Compose NAS` |
|
||||
| `HOMEPAGE_VAR_SEARCH_PROVIDER` | Homepage search provider, [see list here](https://gethomepage.dev/en/widgets/search/) | `google` |
|
||||
| `HOMEPAGE_VAR_HEADER_STYLE` | Homepage header style, [see list here](https://gethomepage.dev/en/configs/settings/#header-style) | `boxed` |
|
||||
| `HOMEPAGE_VAR_WEATHER_CITY` | Homepage weather city name | |
|
||||
| `HOMEPAGE_VAR_WEATHER_LAT` | Homepage weather city latitude | |
|
||||
| `HOMEPAGE_VAR_WEATHER_LONG` | Homepage weather city longitude | |
|
||||
| `HOMEPAGE_VAR_WEATHER_UNIT` | Homepage weather unit, either `metric` or `imperial` | `metric` |
|
||||
| `CALIBRE_USERNAME` | Optional - Calibre-Web username to show details in the homepage, if enabled | `admin` |
|
||||
| `CALIBRE_PASSWORD` | Optional - Calibre-Web password to show details in the homepage, if enabled | `admin123` |
|
||||
5. **Run Initial Configuration Script:**
|
||||
This script helps configure base URLs and API keys within the running *arr applications based on your `.env` file.
|
||||
```bash
|
||||
./update-config.sh
|
||||
```
|
||||
*(Note: You might need to make it executable first: `chmod +x ./update-config.sh`)*
|
||||
|
||||
## PIA WireGuard VPN
|
||||
6. **Access Services:** Once Tailscale is connected, you should be able to access your services via `https://<TAILSCALE_HOSTNAME>.<your-tailnet-name>.ts.net/<service_path>` or `https://<TAILSCALE_IP>/<service_path>`. If you set up DNS for your `HOSTNAME`, you can use `https://<HOSTNAME>/<service_path>`. The main dashboard is at `/`.
|
||||
|
||||
I chose PIA since it supports WireGuard and [port forwarding](https://github.com/thrnz/docker-wireguard-pia/issues/26#issuecomment-868165281),
|
||||
but you could use other providers:
|
||||
## Configuration (`.env` File)
|
||||
|
||||
- OpenVPN: [linuxserver/openvpn-as](https://hub.docker.com/r/linuxserver/openvpn-as)
|
||||
- WireGuard: [linuxserver/wireguard](https://hub.docker.com/r/linuxserver/wireguard)
|
||||
- NordVPN + OpenVPN: [bubuntux/nordvpn](https://hub.docker.com/r/bubuntux/nordvpn/dockerfile)
|
||||
- NordVPN + WireGuard (NordLynx): [bubuntux/nordlynx](https://hub.docker.com/r/bubuntux/nordlynx)
|
||||
This file controls all the essential settings for your Docker Compose stack. Copy `.env.example` to `.env` and edit the values.
|
||||
|
||||
For PIA + WireGuard, fill `.env` and fill it with your PIA credentials.
|
||||
---
|
||||
|
||||
The location of the server it will connect to is set by `LOC=ca`, defaulting to Montreal - Canada.
|
||||
### **Core Settings (Required)**
|
||||
|
||||
You need to fill the credentials in the `PIA_*` environment variable,
|
||||
otherwise the VPN container will exit and qBittorrent will not start.
|
||||
These are fundamental for basic operation and permissions.
|
||||
|
||||
## Sonarr, Radarr & Lidarr
|
||||
* `USER_ID`: The Linux user ID that the containers will run as. Find yours with `id -u`.
|
||||
* *Default:* `1000`
|
||||
* `GROUP_ID`: The Linux group ID that the containers will run as. Find yours with `id -g`.
|
||||
* *Default:* `1000`
|
||||
* **Note:** Using the correct IDs is crucial for file permissions, especially for accessing media files on the host.
|
||||
* `TIMEZONE`: Your local timezone (e.g., `America/New_York`, `Europe/London`, `Asia/Manila`). Find yours from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
||||
* *Default:* `America/New_York`
|
||||
* `HOSTNAME`: **(Deprecated - Now derived)** The primary hostname used by Traefik for routing. This is now automatically constructed from `TAILSCALE_HOSTNAME` and `TAILSCALE_TAILNET_DOMAIN`. You generally don't need to set this directly unless overriding the default behavior.
|
||||
* *Default:* `${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`
|
||||
|
||||
### File Structure
|
||||
---
|
||||
|
||||
Sonarr, Radarr, and Lidarr must be configured to support hardlinks, to allow instant moves and prevent using twice the storage
|
||||
(Bittorrent downloads and final file). The trick is to use a single volume shared by the Bittorrent client and the *arrs.
|
||||
Subfolders are used to separate the TV shows from the movies.
|
||||
### **Host Paths (Required)**
|
||||
|
||||
The configuration is well explained by [this guide](https://trash-guides.info/Hardlinks/How-to-setup-for/Docker/).
|
||||
Define where container data and configuration are stored on your host machine.
|
||||
|
||||
In summary, the final structure of the shared volume will be as follows:
|
||||
* `CONFIG_ROOT`: The base directory on your host where configuration files for each service will be stored. Using `.` stores them in subdirectories within the project folder.
|
||||
* *Default:* `.`
|
||||
* `DATA_ROOT`: The main directory on your host containing your media libraries (movies, TV shows, music, books).
|
||||
* *Default:* `/mnt/data`
|
||||
* `DOWNLOAD_ROOT`: The directory on your host where the download client (qBittorrent/SABnzbd) will store downloads in progress and completed files *before* they are imported by *arr apps.
|
||||
* *Default:* `/mnt/data/torrents`
|
||||
* **Hardlink Note:** For efficient storage (avoiding duplicate files), it's highly recommended that `DOWNLOAD_ROOT` is on the **same filesystem** as `DATA_ROOT` (e.g., `/mnt/data/torrents` is inside `/mnt/data`). This allows instant moves via hardlinks instead of slow copies.
|
||||
|
||||
```
|
||||
data
|
||||
├── torrents = shared folder qBittorrent downloads
|
||||
│ ├── movies = movies downloads tagged by Radarr
|
||||
│ └── tv = movies downloads tagged by Sonarr
|
||||
└── media = shared folder for Sonarr and Radarr files
|
||||
├── movies = Radarr
|
||||
└── tv = Sonarr
|
||||
└── music = Lidarr
|
||||
```
|
||||
---
|
||||
|
||||
Go to Settings > Management.
|
||||
In Sonarr, set the Root folder to `/data/media/tv`.
|
||||
In Radarr, set the Root folder to `/data/media/movies`.
|
||||
In Lidarr, set the Root folder to `/data/media/music`.
|
||||
### **Tailscale Access (Required)**
|
||||
|
||||
### Download Client
|
||||
Controls secure remote access via Tailscale.
|
||||
|
||||
Then qBittorrent can be configured at Settings > Download Clients. Because all the networking for qBittorrent takes
|
||||
place in the VPN container, the hostname for qBittorrent is the hostname of the VPN container, ie `vpn`, and the port is `8080`:
|
||||
* `TAILSCALE_AUTHKEY`: **Required.** An authentication key from your Tailscale account. Generate one in the Tailscale Admin Console under Settings > Keys. You can use a reusable key or an ephemeral key (recommended for containers).
|
||||
* *Default:* (None - **Must be set**)
|
||||
* `TAILSCALE_HOSTNAME`: The desired hostname for this NAS within your Tailscale network.
|
||||
* *Default:* `tailscale-nas`
|
||||
* `TAILSCALE_TAILNET_DOMAIN`: **Required.** The domain of your Tailnet, including your Tailnet's unique name
|
||||
* *Default:* `your-tailnet.ts.net` (**Must be set**)
|
||||
* `TAILSCALE_TAGS`: Optional tags to apply to the Tailscale node (e.g., `tag:nas`).
|
||||
* *Default:* `tag:nas`
|
||||
* `ENABLE_FUNNEL_HTTPS`: Controls Tailscale's public accessibility.
|
||||
* `true`: Enables Tailscale Funnel, making services accessible publicly via the Tailscale domain (`<TAILSCALE_HOSTNAME>.<your-tailnet-name>.ts.net`). Use with caution.
|
||||
* `false`: Uses Tailscale Serve, making services accessible *only* to devices logged into your Tailnet. (Recommended)
|
||||
* *Default:* `false`
|
||||
|
||||
## Prowlarr
|
||||
---
|
||||
|
||||
The indexers are configured through Prowlarr. They synchronize automatically to Radarr and Sonarr.
|
||||
### **Homepage Widgets (Optional)**
|
||||
|
||||
Radarr and Sonarr may then be added via Settings > Apps. The Prowlarr server is `http://prowlarr:9696/prowlarr`, the Radarr server
|
||||
is `http://radarr:7878/radarr` Sonarr `http://sonarr:8989/sonarr`, and Lidarr `http://lidarr:8686/lidarr`.
|
||||
API keys needed *only* if you want to display real-time information from these services on the Homepage dashboard. Find the API keys within each application's settings (usually under Settings > General or Settings > Security).
|
||||
|
||||
Their API keys can be found in Settings > Security > API Key.
|
||||
* `SONARR_API_KEY`
|
||||
* `RADARR_API_KEY`
|
||||
* `LIDARR_API_KEY` (If Lidarr profile is enabled)
|
||||
* `BAZARR_API_KEY`
|
||||
* `PROWLARR_API_KEY`
|
||||
* `JELLYFIN_API_KEY`
|
||||
* `JELLYSEERR_API_KEY`
|
||||
* `SABNZBD_API_KEY` (If SABnzbd profile is enabled)
|
||||
* `ADGUARD_USERNAME` / `ADGUARD_PASSWORD` (If AdGuard Home profile is enabled)
|
||||
* `CALIBRE_USERNAME` / `CALIBRE_PASSWORD` (If Calibre-Web profile is enabled)
|
||||
|
||||
## qBittorrent
|
||||
---
|
||||
|
||||
Running `update-config.sh` will set qBittorrent's password to `adminadmin`. If you wish to update the password manually,
|
||||
since qBittorrent v4.6.2, a temporary password is generated on startup. Get it with `docker compose logs qbittorrent`:
|
||||
```
|
||||
The WebUI administrator username is: admin
|
||||
The WebUI administrator password was not set. A temporary password is provided for this session: <some_password>
|
||||
```
|
||||
### **Homepage Customization (Optional)**
|
||||
|
||||
Use this password to access the UI, then go to Settings > Web UI and set your own password,
|
||||
then set it in `.env`'s `QBITTORRENT_PASSWORD` variable.
|
||||
Control the appearance and behavior of the Homepage dashboard.
|
||||
|
||||
The login page can be disabled on for the local network in by enabling `Bypass authentication for clients`.
|
||||
* `HOMEPAGE_VAR_TITLE`: Title shown on the dashboard.
|
||||
* *Default:* `Docker-Compose NAS`
|
||||
* `HOMEPAGE_VAR_SEARCH_PROVIDER`: Default search engine. [See options](https://gethomepage.dev/en/widgets/search/).
|
||||
* *Default:* `google`
|
||||
* `HOMEPAGE_VAR_HEADER_STYLE`: Dashboard header style. [See options](https://gethomepage.dev/en/configs/settings/#header-style).
|
||||
* *Default:* `boxed`
|
||||
* `HOMEPAGE_VAR_WEATHER_CITY`, `_LAT`, `_LONG`, `_UNIT`: Configure the weather widget.
|
||||
|
||||
```
|
||||
192.168.0.0/16
|
||||
127.0.0.0/8
|
||||
172.17.0.0/16
|
||||
```
|
||||
---
|
||||
|
||||
Set the default save path to `/data/torrents` in Settings, and restrict the network interface to WireGuard (`wg0`).
|
||||
### **Download Client Settings**
|
||||
|
||||
To use the VueTorrent WebUI just go to `qBittorrent`, `Options`, `Web UI`, `Use Alternative WebUI`, and enter `/vuetorrent`. Special thanks to gabe565 for the easy enablement with (https://github.com/gabe565/linuxserver-mod-vuetorrent).
|
||||
Credentials for included download clients.
|
||||
|
||||
## Jellyfin
|
||||
* `QBITTORRENT_USERNAME`: Username for qBittorrent Web UI.
|
||||
* *Default:* `admin`
|
||||
* `QBITTORRENT_PASSWORD`: Password for qBittorrent Web UI.
|
||||
* *Default:* `adminadmin`
|
||||
* **Note:** On first run, qBittorrent might generate a temporary password shown in its logs (`docker compose logs qbittorrent`). Log in with that, change the password in qBittorrent settings, and update this `.env` variable accordingly.
|
||||
|
||||
To enable [hardware transcoding](https://jellyfin.org/docs/general/administration/hardware-acceleration/),
|
||||
depending on your system, you may need to add the following block:
|
||||
---
|
||||
|
||||
```
|
||||
devices:
|
||||
- /dev/dri/renderD128:/dev/dri/renderD128
|
||||
- /dev/dri/card0:/dev/dri/card0
|
||||
```
|
||||
### **VPN Configuration (Example: PIA - Optional)**
|
||||
|
||||
Generally, running Docker on Linux you will want to use VA-API, but the exact mount paths may differ depending on your
|
||||
hardware.
|
||||
These variables are specific to the example `thrnz/docker-wireguard-pia` VPN container used for qBittorrent in the default setup. If you use a different VPN provider or container, you'll need different variables. **If you don't use the VPN, you can ignore these.**
|
||||
|
||||
## Homepage
|
||||
* `PIA_USER`: Private Internet Access username.
|
||||
* `PIA_PASS`: Private Internet Access password.
|
||||
* `PIA_LOCATION`: PIA server location code (e.g., `ca_montreal`, `us_east`). [See list](https://serverlist.piaservers.net/vpninfo/servers/v6).
|
||||
* *Default:* `ca`
|
||||
* `PIA_LOCAL_NETWORK`: Your local network CIDR (e.g., `192.168.1.0/24`). Allows local access to the qBittorrent UI when the VPN is active.
|
||||
* *Default:* `192.168.0.0/16`
|
||||
|
||||
The homepage comes with sensible defaults; some settings can ben controlled via environment variables in `.env`.
|
||||
---
|
||||
|
||||
If you to customize further, you can modify the files in `/homepage/*.yaml` according to the [documentation](https://gethomepage.dev).
|
||||
Due to how the Docker socket is configured for the Docker integration, files must be edited as root.
|
||||
### **Traefik DNS Challenge (Optional)**
|
||||
|
||||
The files in `/homepage/tpl/*.yaml` only serve as a base to set up the homepage configuration on first run.
|
||||
These settings are for enabling automatic HTTPS certificate generation via Let's Encrypt using the DNS-01 challenge method. **This is generally NOT needed** because Tailscale handles HTTPS termination by default in this setup. Only configure this if you have a specific reason to manage your own certificates via Traefik (e.g., accessing services without Tailscale).
|
||||
|
||||
## Jellyseerr
|
||||
* `DNS_CHALLENGE`: Set to `true` to enable DNS challenge.
|
||||
* *Default:* `true` (Consider setting to `false` if using Tailscale for HTTPS)
|
||||
* `DNS_CHALLENGE_PROVIDER`: Your DNS provider supported by Traefik/Lego (e.g., `cloudflare`, `godaddy`). [See providers](https://doc.traefik.io/traefik/https/acme/#providers).
|
||||
* *Default:* `cloudflare`
|
||||
* `LETS_ENCRYPT_EMAIL`: Your email address for Let's Encrypt notifications.
|
||||
* `LETS_ENCRYPT_CA_SERVER`: Let's Encrypt server URL (use staging for testing).
|
||||
* *Default:* `https://acme-v02.api.letsencrypt.org/directory` (Production)
|
||||
* Provider-Specific Variables (e.g., `CLOUDFLARE_EMAIL`, `CLOUDFLARE_DNS_API_TOKEN`, `CLOUDFLARE_ZONE_API_TOKEN`): Credentials required by your chosen `DNS_CHALLENGE_PROVIDER`. Refer to Traefik documentation.
|
||||
|
||||
Jellyseer gives you content recommendations, allows others to make requests to you, and allows logging in with Jellyfin credentials.
|
||||
---
|
||||
|
||||
To set up, go to https://hostname/jellyseerr/setup, and set the URLs as follows:
|
||||
- Jellyfin: http://jellyfin:8096/jellyfin
|
||||
- Radarr:
|
||||
- Hostname: radarr
|
||||
- Port: 7878
|
||||
- URL Base: /radarr
|
||||
- Sonarr
|
||||
- Hostname: sonarr
|
||||
- Port: 8989
|
||||
- URL Base: /sonarr
|
||||
### **Compose Profiles & Files (Advanced)**
|
||||
|
||||
## Traefik and SSL Certificates
|
||||
* `COMPOSE_PROFILES`: Comma-separated list of optional service profiles to enable (e.g., `lidarr,sabnzbd,adguardhome`). See [Optional Services](#optional-services).
|
||||
* `COMPOSE_FILE`: Colon-separated list of compose files to use. Allows extending the base configuration.
|
||||
* *Default:* `docker-compose.yml`
|
||||
|
||||
While you can use the private IP to access your NAS, how cool would it be for it to be accessible through a subdomain
|
||||
with a valid SSL certificate?
|
||||
## Service Access
|
||||
|
||||
Traefik makes this trivial by using Let's Encrypt and one of its
|
||||
[supported ACME challenge providers](https://doc.traefik.io/traefik/https/acme).
|
||||
With the default Tailscale setup, services are securely accessible via HTTPS using your Tailscale node's name or IP, followed by the service path. Replace `<TAILSCALE_NODE>` with your Tailscale device name (e.g., `tailscale-nas.your-tailnet.ts.net`) or its Tailscale IP address.
|
||||
|
||||
Let's assume we are using `nas.domain.com` as custom subdomain.
|
||||
* **Homepage:** `https://<TAILSCALE_NODE>/home`
|
||||
* **Sonarr:** `https://<TAILSCALE_NODE>/sonarr`
|
||||
* **Radarr:** `https://<TAILSCALE_NODE>/radarr`
|
||||
* **Lidarr:** `https://<TAILSCALE_NODE>/lidarr` (If profile enabled)
|
||||
* **Bazarr:** `https://<TAILSCALE_NODE>/bazarr`
|
||||
* **Jellyseerr:** `https://<TAILSCALE_NODE>/jellyseerr`
|
||||
* **Prowlarr:** `https://<TAILSCALE_NODE>/prowlarr`
|
||||
* **qBittorrent:** `https://<TAILSCALE_NODE>/qbittorrent`
|
||||
* **SABnzbd:** `https://<TAILSCALE_NODE>/sabnzbd` (If profile enabled)
|
||||
* **Jellyfin:** `https://<TAILSCALE_NODE>/jellyfin`
|
||||
* **Calibre-Web:** `https://<TAILSCALE_NODE>/calibre` (If profile enabled)
|
||||
* **AdGuard Home:** `http://<TAILSCALE_NODE_IP>:3000` (If profile enabled, access via IP/port initially)
|
||||
* **Tandoor Recipes:** `https://<TAILSCALE_NODE>/recipes` (If profile enabled)
|
||||
* **Joplin Server:** `https://<TAILSCALE_NODE>/joplin` (If profile enabled)
|
||||
* **Home Assistant:** `http://<TAILSCALE_NODE_IP>:8123` (If profile enabled, access via IP/port initially)
|
||||
* **Immich:** `http://<TAILSCALE_NODE_IP>:2283` (If profile enabled, access via IP/port initially)
|
||||
|
||||
The idea is to create an A record pointing to the private IP of the NAS, `192.168.0.10` for example:
|
||||
```
|
||||
nas.domain.com. 1 IN A 192.168.0.10
|
||||
```
|
||||
|
||||
The record will be publicly exposed but not resolve given this is a private IP.
|
||||
|
||||
Given the NAS is not accessible from the internet, we need to do a dnsChallenge.
|
||||
Here we will be using CloudFlare, but the mechanism will be the same for all DNS providers
|
||||
baring environment variable changes, see the Traefik documentation above and [Lego's documentation](https://go-acme.github.io/lego/dns).
|
||||
|
||||
Then, fill the CloudFlare `.env` entries.
|
||||
|
||||
If you want to test your configuration first, use the Let's Encrypt staging server by updating `LETS_ENCRYPT_CA_SERVER`'s
|
||||
value in `.env`:
|
||||
```
|
||||
LETS_ENCRYPT_CA_SERVER=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
```
|
||||
|
||||
If it worked, you will see the staging certificate at https://nas.domain.com.
|
||||
You may remove the `./letsencrypt/acme.json` file and restart the services to issue the real certificate.
|
||||
|
||||
You are free to use any DNS01 provider. Simply replace `DNS_CHALLENGE_PROVIDER` with your own provider,
|
||||
[see complete list here](https://doc.traefik.io/traefik/https/acme/#providers).
|
||||
You will also need to inject the environments variables specific to your provider.
|
||||
|
||||
Certificate generation can be disabled by setting `DNS_CHALLENGE` to `false`.
|
||||
|
||||
### Accessing from the outside with Tailscale
|
||||
|
||||
If we want to make it reachable from outside the network without opening ports or exposing it to the internet, I found
|
||||
[Tailscale](https://tailscale.com) to be a great solution: create a network, run the client on both the NAS and the device
|
||||
you are connecting from, and they will see each other.
|
||||
|
||||
In this case, the A record should point to the IP Tailscale assigned to the NAS, eg `100.xxx.xxx.xxx`:
|
||||
```
|
||||
nas.domain.com. 1 IN A 100.xxx.xxx.xxx
|
||||
```
|
||||
|
||||
See [here](https://tailscale.com/kb/installation) for installation instructions.
|
||||
|
||||
However, this means you will always need to be connected to Tailscale to access your NAS, even locally.
|
||||
This can be remedied by overriding the DNS entry for the NAS domain like `192.168.0.10 nas.domain.com`
|
||||
in your local DNS resolver such as Pi-Hole.
|
||||
|
||||
This way, when connected to the local network, the NAS is accessible directly from the private IP,
|
||||
and from the outside you need to connect to Tailscale first, then the NAS domain will be accessible.
|
||||
**Note:**
|
||||
* `<TAILSCALE_NODE>` refers to the full Tailscale name (e.g., `tailscale-nas.your-tailnet.ts.net`).
|
||||
* `<TAILSCALE_NODE_IP>` refers to the Tailscale IP address of the NAS.
|
||||
* Some services (AdGuard, HA, Immich) might require initial setup via their direct IP and port before Tailscale/Traefik routing is fully effective or configured within the application. Authentication for most services will be handled by Authelia (configured later).
|
||||
|
||||
## Optional Services
|
||||
|
||||
Optional services are not launched by default and enabled by appending their profile name to the
|
||||
`COMPOSE_PROFILES` environment variable (see [Docker documentation](https://docs.docker.com/compose/profiles)).
|
||||
Several services are included but disabled by default. Enable them by adding their profile name to the `COMPOSE_PROFILES` variable in your `.env` file (separate multiple profiles with commas).
|
||||
|
||||
Say you want to enable FlareSolverr, you should have `COMPOSE_PROFILES=flaresolverr`.
|
||||
|
||||
Multiple optional services can be enabled separated by commas: `COMPOSE_PROFILES=flaresolverr,adguardhome`.
|
||||
|
||||
### FlareSolverr
|
||||
|
||||
In Prowlarr, add the FlareSolverr indexer with the URL http://flaresolverr:8191/
|
||||
|
||||
### SABnzbd
|
||||
|
||||
Enable SABnzbd by setting `COMPOSE_PROFILES=sabnzbd`. It will be accessible at `/sabnzbd`.
|
||||
|
||||
If that is not the case, the `url_base` parameter in `sabnzbd.ini` should be set to `/sabnzbd`.
|
||||
|
||||
Additionally, `host_whitelist` value should be set to your hostname.
|
||||
|
||||
### AdGuard Home
|
||||
|
||||
Enable AdGuard Home by setting `COMPOSE_PROFILES=adguardhome`.
|
||||
|
||||
Set the `ADGUARD_HOSTNAME`, I chose a different subdomain to use secure DNS without the folder.
|
||||
|
||||
On first run, specify the port 3000 and enable listen on all interfaces to make it work with Tailscale.
|
||||
|
||||
If after running `docker compose up -d`, you're getting `network docker-compose-nas declared as external, but could not be found`,
|
||||
run `docker network create docker-compose-nas` first.
|
||||
|
||||
#### Encryption
|
||||
|
||||
In Settings > Encryption Settings, set the certificates path to `/opt/adguardhome/certs/certs/<YOUR_HOSTNAME>.crt`
|
||||
and the private key to `/opt/adguardhome/certs/private/<YOUR_HOSTNAME>.key`, those files are created by Traefik cert dumper
|
||||
from the ACME certificates Traefik generates in JSON.
|
||||
|
||||
#### DHCP
|
||||
|
||||
If you want to use the AdGuard Home DHCP server, for example because your router does not allow changing its DNS server,
|
||||
you will need to select the `eth0` DHCP interface matching `10.0.0.10`, then specify the
|
||||
Gateway IP to match your router address (`192.168.0.1` for example) and set a range of IP addresses assigned to local
|
||||
devices.
|
||||
|
||||
In `adguardhome/docker-compose.yml`, set the network interface `dhcp-relay` should listen to. By default, it is set to
|
||||
`enp2s0`, but you may need to change it to your host's network interface, verify it with `ip a`.
|
||||
|
||||
In the configuration (`adguardhome/conf/AdGuardHome.yaml`), set the DHCP options 6th key to your NAS internal IP address:
|
||||
```yml
|
||||
dhcp:
|
||||
dhcpv4:
|
||||
options:
|
||||
- 6 ips 192.168.0.10,192.168.0.10
|
||||
Example: Enable Lidarr and SABnzbd
|
||||
```dotenv
|
||||
COMPOSE_PROFILES=lidarr,sabnzbd
|
||||
```
|
||||
|
||||
Enable DHCP Relay by setting `COMPOSE_PROFILES=adguardhome-dhcp`.
|
||||
Available Profiles:
|
||||
* `lidarr`: Music management.
|
||||
* `sabnzbd`: Usenet download client.
|
||||
* `flaresolverr`: Bypasses Cloudflare challenges for Prowlarr.
|
||||
* `adguardhome`: Network-wide ad blocking (see `adguardhome/README.md`).
|
||||
* `calibre-web`: E-book library management.
|
||||
* `decluttarr`: Automated download cleanup.
|
||||
* `tandoor`: Recipe management (see `tandoor/README.md`).
|
||||
* `joplin`: Note-taking server (see `joplin/README.md`).
|
||||
* `homeassistant`: Home automation (see `homeassistant/README.md`).
|
||||
* `immich`: Photo management (see `immich/README.md`).
|
||||
|
||||
#### Expose DNS Server with Tailscale
|
||||
## Troubleshooting
|
||||
|
||||
Based on [Tailscale's documentation](https://tailscale.com/kb/1114/pi-hole), it is easy to use your AdGuard server everywhere.
|
||||
Just make sure that AdGuard Home listens to all interfaces.
|
||||
### SELinux Socket Permissions (Docker)
|
||||
|
||||
### Calibre-Web
|
||||
If you are running Docker on a host with SELinux enabled (like Fedora, CentOS, RHEL) and services like Traefik, Watchtower, or Autoheal fail with "permission denied" errors when trying to access `/var/run/docker.sock`:
|
||||
|
||||
If you do not have a Calibre database, download a sample from: https://github.com/janeczku/calibre-web/raw/master/library/metadata.db
|
||||
and place it in `${DATA_ROOT}/books`.
|
||||
1. **Check Audit Logs:** Immediately after seeing the error, check the SELinux audit log on the host:
|
||||
```bash
|
||||
sudo ausearch -m avc -ts recent
|
||||
```
|
||||
Look for lines containing `denied`, `docker.sock`, and the name of the failing service (e.g., `traefik`, `watchtower`).
|
||||
|
||||
On the initial setup screen, enter `/books` as your calibre library location.
|
||||
2. **Generate Custom Policy:** If denials are found, you may need to create a custom SELinux policy module using `audit2allow`. Pipe the denial messages into it:
|
||||
```bash
|
||||
# Generate policy files (my-dockersock.te and my-dockersock.pp)
|
||||
sudo ausearch -m avc -ts recent | audit2allow -M my-dockersock
|
||||
|
||||
**Default admin login:** Username: `admin` Password: `admin123`.
|
||||
# Install the policy module
|
||||
sudo semodule -i my-dockersock.pp
|
||||
```
|
||||
This allows the specific actions that were being denied. You might need to repeat this if different denials appear after applying the first policy.
|
||||
|
||||
Unrar is included by default and needs to be set in the Calibre-Web admin page (Basic Configuration:External Binaries)
|
||||
with a path of `/usr/bin/unrar`.
|
||||
### Tailscale Issues
|
||||
|
||||
### Decluttarr
|
||||
Decluttarr keeps the queue free of stalled and redundant downloads. For configuration options and examples,
|
||||
please see https://github.com/ManiMatter/decluttarr/blob/dev/README.md.
|
||||
* **Authentication:** Ensure your `TAILSCALE_AUTHKEY` in `.env` is valid and hasn't expired (especially if using ephemeral keys). Check the `tailscale` container logs (`docker compose logs tailscale`) for authentication errors.
|
||||
* **Connectivity:** Verify the `tailscale` container is running and connected to your Tailnet (`docker compose exec tailscale tailscale status`).
|
||||
* **Funnel/Serve Command:** If you modified the Tailscale command, ensure the syntax for `tailscale funnel` or `tailscale serve` is correct.
|
||||
|
||||
All environment variables are prefixed with `DECLUTTARR_`.
|
||||
### File Permissions
|
||||
|
||||
### Tandoor
|
||||
If services report permission errors when accessing `/config` or `/data` directories, double-check that:
|
||||
* The `USER_ID` and `GROUP_ID` in your `.env` file match the owner/group of the corresponding `CONFIG_ROOT` and `DATA_ROOT` directories on your host.
|
||||
* The host directories have appropriate read/write permissions for that user/group.
|
||||
* If using SELinux, the `:Z` flag on the volume mounts in `docker-compose.yml` is correctly applied to allow the container to write to the host paths.
|
||||
|
||||
See [here](./tandoor/README.md).
|
||||
## Advanced Topics
|
||||
|
||||
### Joplin
|
||||
*(Relevant sections like Synology Quirks, NFS Share, Static IP, etc., can be kept here, but review them to ensure they align with a standard Docker setup rather than Podman specifics where applicable.)*
|
||||
|
||||
See [here](./joplin/README.md).
|
||||
*(Example: Synology section should focus on Docker package setup, port conflicts, user IDs, etc., relevant to DSM.)*
|
||||
|
||||
### Home Assistant
|
||||
*(Example: Remove Podman-specific commands or troubleshooting steps from these sections.)*
|
||||
|
||||
See [here](./homeassistant/README.md).
|
||||
---
|
||||
|
||||
### Immich
|
||||
|
||||
See [here](./immich/README.md).
|
||||
|
||||
## Customization
|
||||
|
||||
You can override the configuration of a service or add new services by creating a new `docker-compose.override.yml` file,
|
||||
then appending it to the `COMPOSE_FILE` environment variable: `COMPOSE_FILE=docker-compose.yml:docker-compose.override.yml`
|
||||
|
||||
[See official documentation](https://docs.docker.com/compose/extends).
|
||||
|
||||
For example, use a [different VPN provider](https://github.com/bubuntux/nordvpn):
|
||||
|
||||
```yml
|
||||
services:
|
||||
vpn:
|
||||
image: ghcr.io/bubuntux/nordvpn
|
||||
cap_add:
|
||||
- NET_ADMIN # Required
|
||||
- NET_RAW # Required
|
||||
environment: # Review https://github.com/bubuntux/nordvpn#environment-variables
|
||||
- USER=user@email.com # Required
|
||||
- "PASS=pas$word" # Required
|
||||
- CONNECT=United_States
|
||||
- TECHNOLOGY=NordLynx
|
||||
- NETWORK=192.168.1.0/24 # So it can be accessed within the local network
|
||||
```
|
||||
|
||||
### Optional: Using the VPN for *arr apps
|
||||
|
||||
If you want to use the VPN for Prowlarr and other *arr applications, add the following block to all the desired containers:
|
||||
```yml
|
||||
network_mode: "service:vpn"
|
||||
depends_on:
|
||||
vpn:
|
||||
condition: service_healthy
|
||||
```
|
||||
|
||||
Change the healthcheck to mark the containers as unhealthy when internet connection is not working by appending a URL
|
||||
to the healthcheck, eg: `test: [ "CMD", "curl", "--fail", "http://127.0.0.1:7878/radarr/ping", "https://google.com" ]`
|
||||
|
||||
Then in Prowlarr, use `localhost` rather than `vpn` as the hostname, since they are on the same network.
|
||||
|
||||
## Synology Quirks
|
||||
|
||||
Docker compose NAS can run on DSM 7.1, with a few extra steps.
|
||||
|
||||
### Free Ports 80 and 443
|
||||
|
||||
By default, ports 80 and 443 are used by Nginx but not actually used for anything useful. Free them by creating a new task
|
||||
in the Task Scheduler > Create > Triggered Task > User-defined script. Leave the Event as `Boot-up` and the `root` user,
|
||||
go to Task Settings and paste the following in User-defined script:
|
||||
```
|
||||
sed -i -e 's/80/81/' -e 's/443/444/' /usr/syno/share/nginx/server.mustache /usr/syno/share/nginx/DSM.mustache /usr/syno/share/nginx/WWWService.mustache
|
||||
|
||||
synosystemctl restart nginx
|
||||
```
|
||||
|
||||
### Install Synology WireGuard
|
||||
|
||||
Since WireGuard is not part of DSM's kernel, an external package must be installed for the `vpn` container to run.
|
||||
|
||||
For DSM 7.1, download and install the package corresponding to your NAS CPU architecture
|
||||
[from here](https://github.com/vegardit/synology-wireguard/releases).
|
||||
|
||||
As specified in the [project's README](https://github.com/vegardit/synology-wireguard#installation),
|
||||
the package must be run as `root` from the command line: `sudo /var/packages/WireGuard/scripts/start`
|
||||
|
||||
### Free Port 1900
|
||||
|
||||
Jellyfin will fail to run by default since the port 1900
|
||||
[is not free](https://lookanotherblog.com/resolve-port-1900-conflict-between-plex-and-synology/).
|
||||
You may free it by going to Control Panel > File Services > Advanced > SSTP > Untick `Enable Windows network discovery`.
|
||||
|
||||
### User Permissions
|
||||
|
||||
By default, the user and groups are set to `1000` as it is the default on Ubuntu and many other Linux distributions.
|
||||
However, that is not the case in Synology; the first user should have an ID of `1026` and a group of `100`.
|
||||
You may check yours with `id`.
|
||||
Update the `USER_ID` and `GROUP_ID` in `.env` with your IDs.
|
||||
Not updating them may result in [permission issues](https://github.com/AdrienPoupa/docker-compose-nas/issues/10).
|
||||
|
||||
```
|
||||
USER_ID=1026
|
||||
GROUP_ID=100
|
||||
```
|
||||
|
||||
### Synology DHCP Server and Adguard Home Port Conflict
|
||||
|
||||
If you are using the Synology DHCP Server package, it will use port 53 even if it does not need it. This is because
|
||||
it uses Dnsmasq to handle DHCP requests, but does not serve DNS queries. The port can be released by editing (as root)
|
||||
`/usr/local/lib/systemd/system/pkg-dhcpserver.service` and [adding -p 0](https://www.reddit.com/r/synology/comments/njwdao/comment/j2d23qr/?utm_source=reddit&utm_medium=web2x&context=3):
|
||||
`ExecStart=/var/packages/DhcpServer/target/dnsmasq-2.x/usr/bin/dnsmasq --user=DhcpServer --group=DhcpServer --cache-size=200 --conf-file=/etc/dhcpd/dhcpd.conf --dhcp-lease-max=2147483648 -p 0`
|
||||
Reboot the NAS and the port 53 will be free for Adguard.
|
||||
|
||||
## Use Separate Paths for Torrents and Storage
|
||||
|
||||
If you want to use separate paths for torrents download and long term storage, to use different disks for example,
|
||||
set your `docker-compose.override.yml` to:
|
||||
|
||||
```yml
|
||||
services:
|
||||
sonarr:
|
||||
volumes:
|
||||
- ./sonarr:/config
|
||||
- ${DATA_ROOT}/media/tv:/data/media/tv
|
||||
- ${DOWNLOAD_ROOT}/tv:/data/torrents/tv
|
||||
radarr:
|
||||
volumes:
|
||||
- ./radarr:/config
|
||||
- ${DATA_ROOT}/media/movies:/data/media/movies
|
||||
- ${DOWNLOAD_ROOT}/movies:/data/torrents/movies
|
||||
```
|
||||
|
||||
Note you will lose the hard link ability, ie your files will be duplicated.
|
||||
|
||||
In Sonarr and Radarr, go to `Settings` > `Importing` > Untick `Use Hardlinks instead of Copy`
|
||||
|
||||
## NFS Share
|
||||
|
||||
This can be useful to share the media folder to a local player like Kodi or computers in the local network,
|
||||
but may not be necessary if Jellyfin is going to be used to access the media.
|
||||
|
||||
Install the NFS kernel server: `sudo apt install nfs-kernel-server`
|
||||
|
||||
Then edit `/etc/exports` to configure your shares:
|
||||
|
||||
`/mnt/data/media 192.168.0.0/255.255.255.0(rw,all_squash,nohide,no_subtree_check,anonuid=1000,anongid=1000)`
|
||||
|
||||
This will share the `media` folder to anybody on your local network (192.168.0.x).
|
||||
I purposely left out the `sync` flag that would slow down file transfer.
|
||||
On [some devices](https://forum.kodi.tv/showthread.php?tid=343434) you may need to use the `insecure`
|
||||
option for the share to be available.
|
||||
|
||||
Restart the NFS server to apply the changes: `sudo /etc/init.d/nfs-kernel-server restart`
|
||||
|
||||
On other machines, you can see the shared folder by adding the following to your `/etc/fstab`:
|
||||
|
||||
`192.168.0.10:/mnt/data/media /mnt/nas nfs ro,hard,intr,auto,_netdev 0 0`
|
||||
|
||||
## Static IP
|
||||
|
||||
Set a static IP, assuming `192.168.0.10` and using Google DNS servers: `sudo nano /etc/netplan/00-installer-config.yaml`
|
||||
|
||||
```yaml
|
||||
# This is the network config written by 'subiquity'
|
||||
network:
|
||||
ethernets:
|
||||
enp2s0:
|
||||
dhcp4: no
|
||||
addresses:
|
||||
- 192.168.0.10/24
|
||||
gateway4: 192.168.0.1
|
||||
nameservers:
|
||||
addresses: [8.8.8.8, 8.8.4.4]
|
||||
version: 2
|
||||
```
|
||||
|
||||
Apply the plan: `sudo netplan apply`. You can check the server uses the right IP with `ip a`.
|
||||
|
||||
## Laptop Specific Configuration
|
||||
|
||||
If the server is installed on a laptop, you may want to disable the suspension when the lid is closed:
|
||||
`sudo nano /etc/systemd/logind.conf`
|
||||
|
||||
Replace:
|
||||
- `#HandleLidSwitch=suspend` by `HandleLidSwitch=ignore`
|
||||
- `#LidSwitchIgnoreInhibited=yes` by `LidSwitchIgnoreInhibited=no`
|
||||
|
||||
Then restart: `sudo service systemd-logind restart`
|
||||
*Self-hosted media stack powered by Docker, Traefik, Tailscale, and the \*arr suite.*
|
||||
|
||||
@@ -3,38 +3,24 @@ services:
|
||||
image: ghcr.io/traefik/traefik:3.3
|
||||
container_name: traefik
|
||||
restart: always
|
||||
environment:
|
||||
- CLOUDFLARE_EMAIL=${CLOUDFLARE_EMAIL}
|
||||
- CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}
|
||||
- CLOUDFLARE_ZONE_API_TOKEN=${CLOUDFLARE_ZONE_API_TOKEN}
|
||||
- LETS_ENCRYPT_EMAIL=${LETS_ENCRYPT_EMAIL}
|
||||
command:
|
||||
- --ping=true
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.web-secure.address=:443
|
||||
- --entrypoints.web.http.redirections.entryPoint.to=web-secure
|
||||
- --entrypoints.web.http.redirections.entryPoint.scheme=https
|
||||
- --entrypoints.web.http.redirections.entrypoint.permanent=true
|
||||
- --experimental.plugins.rewrite-body.modulename=github.com/packruler/rewrite-body
|
||||
- --experimental.plugins.rewrite-body.version=v1.2.0
|
||||
- --experimental.plugins.rewriteHeaders.modulename=github.com/XciD/traefik-plugin-rewrite-headers
|
||||
- --experimental.plugins.rewriteHeaders.version=v0.0.3
|
||||
- --certificatesresolvers.myresolver.acme.dnschallenge=${DNS_CHALLENGE:-true}
|
||||
- --certificatesresolvers.myresolver.acme.dnschallenge.provider=${DNS_CHALLENGE_PROVIDER:-cloudflare}
|
||||
- --certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53
|
||||
- --certificatesresolvers.myresolver.acme.caserver=${LETS_ENCRYPT_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory}
|
||||
- --certificatesresolvers.myresolver.acme.email=${LETS_ENCRYPT_EMAIL}
|
||||
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
network_mode: service:tailscale # Add this line
|
||||
# ports: # Remove this section
|
||||
# - "80:80"
|
||||
# - "443:443"
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/letsencrypt:/letsencrypt
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
extra_hosts:
|
||||
- host.docker.internal:172.17.0.1
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
# extra_hosts: # Remove this section
|
||||
# - host.docker.internal:172.17.0.1
|
||||
healthcheck:
|
||||
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
||||
interval: 30s
|
||||
@@ -47,8 +33,8 @@ services:
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/sonarr:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${CONFIG_ROOT:-.}/sonarr:/config:Z
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://127.0.0.1:8989/sonarr/ping"]
|
||||
@@ -56,9 +42,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.sonarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/sonarr`))
|
||||
- traefik.http.routers.sonarr.tls=true
|
||||
- traefik.http.routers.sonarr.tls.certresolver=myresolver
|
||||
- traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`)
|
||||
- traefik.http.routers.sonarr.entrypoints=web
|
||||
- traefik.http.services.sonarr.loadbalancer.server.port=8989
|
||||
- homepage.group=Media
|
||||
- homepage.name=Sonarr
|
||||
@@ -77,8 +62,8 @@ services:
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/radarr:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${CONFIG_ROOT:-.}/radarr:/config:Z
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://127.0.0.1:7878/radarr/ping"]
|
||||
@@ -86,9 +71,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.radarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/radarr`))
|
||||
- traefik.http.routers.radarr.tls=true
|
||||
- traefik.http.routers.radarr.tls.certresolver=myresolver
|
||||
- traefik.http.routers.radarr.rule=PathPrefix(`/radarr`)
|
||||
- traefik.http.routers.radarr.entrypoints=web
|
||||
- traefik.http.services.radarr.loadbalancer.server.port=7878
|
||||
- homepage.group=Media
|
||||
- homepage.name=Radarr
|
||||
@@ -107,8 +91,8 @@ services:
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/lidarr:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${CONFIG_ROOT:-.}/lidarr:/config:Z
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://127.0.0.1:8686/lidarr/ping"]
|
||||
@@ -116,9 +100,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.lidarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/lidarr`))
|
||||
- traefik.http.routers.lidarr.tls=true
|
||||
- traefik.http.routers.lidarr.tls.certresolver=myresolver
|
||||
- traefik.http.routers.lidarr.rule=PathPrefix(`/lidarr`)
|
||||
- traefik.http.routers.lidarr.entrypoints=web
|
||||
- traefik.http.services.lidarr.loadbalancer.server.port=8686
|
||||
- homepage.group=Media
|
||||
- homepage.name=Lidarr
|
||||
@@ -139,8 +122,8 @@ services:
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/bazarr/config:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${CONFIG_ROOT:-.}/bazarr/config:/config:Z
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://127.0.0.1:6767/bazarr/ping"]
|
||||
@@ -148,9 +131,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.bazarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/bazarr`))
|
||||
- traefik.http.routers.bazarr.tls=true
|
||||
- traefik.http.routers.bazarr.tls.certresolver=myresolver
|
||||
- traefik.http.routers.bazarr.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/bazarr`)
|
||||
- traefik.http.routers.bazarr.entrypoints=web
|
||||
- traefik.http.services.bazarr.loadbalancer.server.port=6767
|
||||
- homepage.group=Download
|
||||
- homepage.name=Bazarr
|
||||
@@ -168,7 +150,7 @@ services:
|
||||
- LOG_LEVEL=debug
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/jellyseerr:/app/config
|
||||
- ${CONFIG_ROOT:-.}/jellyseerr:/app/config:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
test:
|
||||
@@ -183,9 +165,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.jellyseerr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/jellyseerr`))
|
||||
- traefik.http.routers.jellyseerr.tls=true
|
||||
- traefik.http.routers.jellyseerr.tls.certresolver=myresolver
|
||||
- traefik.http.routers.jellyseerr.rule=PathPrefix(`/jellyseerr`)
|
||||
- traefik.http.routers.jellyseerr.entrypoints=web
|
||||
- traefik.http.services.jellyseerr.loadbalancer.server.port=5055
|
||||
- traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders
|
||||
- traefik.http.middlewares.jellyseerr-stripprefix.stripPrefix.prefixes=/jellyseerr
|
||||
@@ -250,7 +231,7 @@ services:
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/prowlarr:/config
|
||||
- ${CONFIG_ROOT:-.}/prowlarr:/config:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://127.0.0.1:9696/prowlarr/ping"]
|
||||
@@ -258,9 +239,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.prowlarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/prowlarr`))
|
||||
- traefik.http.routers.prowlarr.tls=true
|
||||
- traefik.http.routers.prowlarr.tls.certresolver=myresolver
|
||||
- traefik.http.routers.prowlarr.rule=PathPrefix(`/prowlarr`)
|
||||
- traefik.http.routers.prowlarr.entrypoints=web
|
||||
- traefik.http.services.prowlarr.loadbalancer.server.port=9696
|
||||
- homepage.group=Download
|
||||
- homepage.name=Prowlarr
|
||||
@@ -283,7 +263,7 @@ services:
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.flaresolverr.rule=PathPrefix(`/flaresolverr`)
|
||||
- traefik.http.routers.flaresolverr.tls=true
|
||||
- traefik.http.routers.flaresolverr.entrypoints=web
|
||||
- traefik.http.services.flaresolverr.loadbalancer.server.port=8191
|
||||
profiles:
|
||||
- flaresolverr
|
||||
@@ -297,8 +277,8 @@ services:
|
||||
- WEBUI_PORT=8080
|
||||
- DOCKER_MODS=ghcr.io/gabe565/linuxserver-mod-vuetorrent
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/qbittorrent:/config
|
||||
- ${DOWNLOAD_ROOT}:/data/torrents
|
||||
- ${CONFIG_ROOT:-.}/qbittorrent:/config:Z
|
||||
- ${DOWNLOAD_ROOT}:/data/torrents:Z
|
||||
restart: always
|
||||
healthcheck:
|
||||
# Container may fail if the PIA's token expired, so mark as unhealthy when there is no internet connection
|
||||
@@ -307,15 +287,10 @@ services:
|
||||
["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"]
|
||||
interval: 30s
|
||||
retries: 10
|
||||
network_mode: "service:vpn"
|
||||
depends_on:
|
||||
vpn:
|
||||
condition: service_healthy
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.qbittorrent.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/qbittorrent`))
|
||||
- traefik.http.routers.qbittorrent.tls=true
|
||||
- traefik.http.routers.qbittorrent.tls.certresolver=myresolver
|
||||
- traefik.http.routers.qbittorrent.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/qbittorrent`)
|
||||
- traefik.http.routers.qbittorrent.entrypoints=web
|
||||
- traefik.http.services.qbittorrent.loadbalancer.server.port=8080
|
||||
- traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix
|
||||
# https://github.com/qbittorrent/qBittorrent/issues/5693#issuecomment-552146296
|
||||
@@ -332,45 +307,14 @@ services:
|
||||
- homepage.description=Bittorrent client
|
||||
- homepage.weight=2
|
||||
- homepage.widget.type=qbittorrent
|
||||
- homepage.widget.url=http://vpn:8080
|
||||
- homepage.widget.url=http://qbittorrent:8080
|
||||
- homepage.widget.username=${QBITTORRENT_USERNAME}
|
||||
- homepage.widget.password=${QBITTORRENT_PASSWORD}
|
||||
vpn:
|
||||
image: ghcr.io/thrnz/docker-wireguard-pia:latest
|
||||
container_name: vpn
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/pia:/pia
|
||||
- ${CONFIG_ROOT:-.}/pia-shared:/pia-shared
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
- LOC=${PIA_LOCATION}
|
||||
- USER=${PIA_USER}
|
||||
- PASS=${PIA_PASS}
|
||||
- QBT_USER=${QBITTORRENT_USERNAME}
|
||||
- QBT_PASS=${QBITTORRENT_PASSWORD}
|
||||
- LOCAL_NETWORK=${PIA_LOCAL_NETWORK}
|
||||
- PORT_FORWARDING=1
|
||||
- PORT_PERSIST=1
|
||||
- PORT_SCRIPT=/pia-shared/portupdate-qbittorrent.sh
|
||||
- FIREWALL=0
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
- net.ipv6.conf.default.disable_ipv6=1
|
||||
- net.ipv6.conf.all.disable_ipv6=1
|
||||
- net.ipv6.conf.lo.disable_ipv6=1
|
||||
healthcheck:
|
||||
test: ping -c 1 www.google.com || exit 1
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: always
|
||||
unpackerr:
|
||||
image: ghcr.io/unpackerr/unpackerr:latest
|
||||
container_name: unpackerr
|
||||
volumes:
|
||||
- ${DOWNLOAD_ROOT}:/data/torrents
|
||||
- ${DOWNLOAD_ROOT}:/data/torrents:Z
|
||||
restart: always
|
||||
user: ${USER_ID}:${GROUP_ID}
|
||||
environment:
|
||||
@@ -389,14 +333,13 @@ services:
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/sabnzbd:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${CONFIG_ROOT:-.}/sabnzbd:/config:Z
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
restart: always
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.sabnzbd.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/sabnzbd`) || PathPrefix(`/sabnzbd`))
|
||||
- traefik.http.routers.sabnzbd.tls=true
|
||||
- traefik.http.routers.sabnzbd.tls.certresolver=myresolver
|
||||
- traefik.http.routers.sabnzbd.rule=PathPrefix(`/sabnzbd`) # Simplified rule
|
||||
- traefik.http.routers.sabnzbd.entrypoints=web
|
||||
- traefik.http.services.sabnzbd.loadbalancer.server.port=8080
|
||||
- homepage.group=Download
|
||||
- homepage.name=Sabnzbd
|
||||
@@ -416,10 +359,10 @@ services:
|
||||
- PUID=${USER_ID}
|
||||
- PGID=${GROUP_ID}
|
||||
- TZ=${TIMEZONE}
|
||||
- JELLYFIN_PublishedServerUrl=${HOSTNAME}/jellyfin
|
||||
- JELLYFIN_PublishedServerUrl=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/jellyfin
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/jellyfin:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${CONFIG_ROOT:-.}/jellyfin:/config:Z
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
ports:
|
||||
- "7359:7359/udp"
|
||||
- "1900:1900/udp"
|
||||
@@ -430,9 +373,8 @@ services:
|
||||
retries: 10
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.jellyfin.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/jellyfin`))
|
||||
- traefik.http.routers.jellyfin.tls=true
|
||||
- traefik.http.routers.jellyfin.tls.certresolver=myresolver
|
||||
- traefik.http.routers.jellyfin.rule=PathPrefix(`/jellyfin`)
|
||||
- traefik.http.routers.jellyfin.entrypoints=web
|
||||
- traefik.http.services.jellyfin.loadbalancer.server.port=8096
|
||||
- homepage.group=Media
|
||||
- homepage.name=Jellyfin
|
||||
@@ -453,8 +395,8 @@ services:
|
||||
- DOCKER_MODS=linuxserver/mods:universal-calibre
|
||||
- OAUTHLIB_RELAX_TOKEN_SCOPE=1
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/calibre-web:/config
|
||||
- ${DATA_ROOT}/books:/books
|
||||
- ${CONFIG_ROOT:-.}/calibre-web:/config:Z
|
||||
- ${DATA_ROOT}/books:/books:Z
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
@@ -462,9 +404,8 @@ services:
|
||||
- traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Script-Name=/calibre
|
||||
- traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre
|
||||
- traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex
|
||||
- traefik.http.routers.calibre.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/calibre`))
|
||||
- traefik.http.routers.calibre.tls=true
|
||||
- traefik.http.routers.calibre.tls.certresolver=myresolver
|
||||
- traefik.http.routers.calibre.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/calibre`)
|
||||
- traefik.http.routers.calibre.entrypoints=web
|
||||
- traefik.http.services.calibre.loadbalancer.server.port=8083
|
||||
- homepage.group=Media
|
||||
- homepage.name=Calibre-Web
|
||||
@@ -526,19 +467,21 @@ services:
|
||||
- HOMEPAGE_VAR_WEATHER_LONG=${HOMEPAGE_VAR_WEATHER_LONG}
|
||||
- HOMEPAGE_VAR_WEATHER_TIME=${TIMEZONE}
|
||||
- HOMEPAGE_VAR_WEATHER_UNIT=${HOMEPAGE_VAR_WEATHER_UNIT}
|
||||
- HOMEPAGE_ALLOWED_HOSTS=${HOSTNAME}
|
||||
# Explicitly allow the hostname constructed from Tailscale variables
|
||||
- HOMEPAGE_ALLOWED_HOSTS=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/homepage:/app/config
|
||||
- ${CONFIG_ROOT:-.}/homepage:/app/config:Z
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${DATA_ROOT}:/data:Z
|
||||
restart: always
|
||||
command:
|
||||
[sh, -c, "cp -n /app/config/tpl/*.yaml /app/config && node server.js"]
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.homepage.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/`))
|
||||
- traefik.http.routers.homepage.tls=true
|
||||
- traefik.http.routers.homepage.tls.certresolver=myresolver
|
||||
# Change path to /home and use specific Tailscale host
|
||||
- traefik.http.routers.homepage.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/home`)
|
||||
- traefik.http.routers.homepage.entrypoints=web
|
||||
# Authelia middleware will be added in a later commit
|
||||
- traefik.http.services.homepage.loadbalancer.server.port=3000
|
||||
watchtower:
|
||||
image: ghcr.io/containrrr/watchtower:latest
|
||||
@@ -556,6 +499,64 @@ services:
|
||||
- AUTOHEAL_CONTAINER_LABEL=all
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
tailscale:
|
||||
image: tailscale/tailscale:latest
|
||||
container_name: tailscale
|
||||
hostname: ${TAILSCALE_HOSTNAME:-tailscale-nas} # Hostname for Tailscale access
|
||||
environment:
|
||||
TS_AUTHKEY: ${TAILSCALE_AUTHKEY} # Needs to be set in .env
|
||||
TS_EXTRA_ARGS: "--advertise-tags=${TAILSCALE_TAGS:-tag:nas}" # Keep tags if desired
|
||||
TS_STATE_DIR: "/var/lib/tailscale"
|
||||
TS_USERSPACE: "false"
|
||||
# Switch to enable Funnel (public access) or Serve (Tailnet only)
|
||||
ENABLE_FUNNEL_HTTPS: ${ENABLE_FUNNEL_HTTPS:-false}
|
||||
volumes:
|
||||
- ${CONFIG_ROOT:-.}/tailscale/state:/var/lib/tailscale:Z # Persist state
|
||||
- /var/run/docker.sock:/var/run/docker.sock # Optional, keep if needed
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
extra_hosts: # Add this section
|
||||
- host.docker.internal:172.17.0.1
|
||||
restart: always
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Starting containerboot (tailscaled)..."
|
||||
/usr/local/bin/containerboot &
|
||||
echo "Waiting for tailscaled to achieve running state..."
|
||||
retries=60
|
||||
count=0
|
||||
until tailscale status --json | grep -q '"BackendState": "Running"'; do
|
||||
count=$$(($$count+1))
|
||||
if [ $$count -gt $$retries ]; then
|
||||
echo "Error: tailscaled did not reach running state after $$retries seconds."
|
||||
exit 1
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo " Tailscaled is running."
|
||||
|
||||
# --- Start Tailscale Funnel/Serve ---
|
||||
# Check the ENABLE_FUNNEL_HTTPS variable
|
||||
if [ "${ENABLE_FUNNEL_HTTPS}" = "true" ]; then
|
||||
echo "ENABLE_FUNNEL_HTTPS is true. Setting up Funnel -> http://localhost:80..."
|
||||
tailscale funnel --bg http://localhost:80
|
||||
echo "Tailscale Funnel configured."
|
||||
else
|
||||
echo "ENABLE_FUNNEL_HTTPS is false. Setting up Serve -> http://localhost:80..."
|
||||
tailscale serve --bg http://localhost:80
|
||||
echo "Tailscale Serve configured."
|
||||
fi
|
||||
# --- End Tailscale Funnel/Serve ---
|
||||
|
||||
echo "Tailscale forwarding configured. Container will remain running."
|
||||
wait # Wait indefinitely for background processes
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user