- Move Stash data mount from MEDIA_SHARE/xxx to /mnt/appdata/xxx - Add dedicated xxx mount to Whisparr for hardlinking compatibility - Fix AdGuard port mapping (3000 -> 80) after initial setup - Update Homepage volumes for disk monitoring (media, backup, appdata) - Add reddit-counter container for reddit media collection stats
1055 lines
34 KiB
YAML
1055 lines
34 KiB
YAML
# Welcome to my Ultimate Plex Stack! (Modernized Fork)
|
|
#
|
|
# Make sure to rename this file to "docker-compose.yaml" if you are using git clone
|
|
#
|
|
# This is a modernized version of the original ultimate-plex-stack
|
|
# Updated to use current tools and replace deprecated ones (2025/2026)
|
|
#
|
|
# Environment Variable Examples:
|
|
# PUID = 99
|
|
# GUID = 101
|
|
# TZ = America/Edmonton
|
|
# BASE_PATH = /home/username/docker
|
|
#
|
|
# https://trash-guides.info/Hardlinks/Hardlinks-and-Instant-Moves/ # This can be useful for establishing how the media will be presented below
|
|
# MEDIA_SHARE = /mnt/media # This can also be renamed to "SHARE" or "MEDIA" this is where you will present your media
|
|
#
|
|
# NOTE: This is not a plug and play solution, some research / customization will be required to make this work as intended
|
|
# Feel free to customize ie: remove/change/add containers as needed - one size does not fit all
|
|
|
|
---
|
|
version: "3.0"
|
|
|
|
networks:
|
|
proxy:
|
|
driver: bridge
|
|
|
|
services:
|
|
# ============================================
|
|
# DNS & AD BLOCKING
|
|
# ============================================
|
|
|
|
#AdGuard Home - Network-wide DNS with ad blocking
|
|
#
|
|
#First run: Access http://SERVER_IP:3001 to complete setup
|
|
#After setup, configure your router/devices to use SERVER_IP as DNS
|
|
adguard:
|
|
image: adguard/adguardhome:latest
|
|
container_name: adguard
|
|
hostname: adguard
|
|
ports:
|
|
- 53:53/tcp
|
|
- 53:53/udp
|
|
- 3001:80/tcp # Admin UI
|
|
- 8443:443/tcp # HTTPS admin
|
|
- 853:853/tcp # DNS-over-TLS
|
|
volumes:
|
|
- ${BASE_PATH}/adguard/work:/opt/adguardhome/work
|
|
- ${BASE_PATH}/adguard/conf:/opt/adguardhome/conf
|
|
networks:
|
|
- proxy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.adguard.rule=Host(`dns.homelab`)"
|
|
- "traefik.http.routers.adguard.entrypoints=websecure"
|
|
- "traefik.http.routers.adguard.tls=true"
|
|
- "traefik.http.services.adguard.loadbalancer.server.port=80"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# REVERSE PROXY
|
|
# ============================================
|
|
|
|
#Traefik - Reverse proxy for friendly URLs
|
|
traefik:
|
|
image: traefik:latest
|
|
container_name: traefik
|
|
command:
|
|
- "--api.insecure=true"
|
|
- "--providers.docker=true"
|
|
- "--providers.docker.exposedbydefault=false"
|
|
- "--entrypoints.web.address=:80"
|
|
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
|
|
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
|
- "--entrypoints.websecure.address=:443"
|
|
- "--providers.file.filename=/etc/traefik/dynamic.yml"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "8888:8080" # Traefik dashboard
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- ${BASE_PATH}/traefik:/etc/traefik
|
|
networks:
|
|
- proxy
|
|
restart: unless-stopped
|
|
|
|
#Plex - used to display the media
|
|
#
|
|
#This can also be replaced by Emby/Jellyfin
|
|
plex:
|
|
image: lscr.io/linuxserver/plex:latest
|
|
container_name: plex
|
|
network_mode: host
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
- VERSION=docker
|
|
- PLEX_CLAIM=${PLEX_CLAIM}
|
|
ports:
|
|
- 32400:32400
|
|
devices:
|
|
- /dev/dri:/dev/dri #Required for plex HW transcoding / QuickSync
|
|
volumes:
|
|
- ${BASE_PATH}/plex/config:/config
|
|
- ${MEDIA_SHARE}/tv:/tv
|
|
- ${MEDIA_SHARE}/movies:/movies
|
|
- ${MEDIA_SHARE}/music:/music
|
|
- ${MEDIA_SHARE}/concerts:/concerts
|
|
restart: unless-stopped
|
|
|
|
#Radarr - used to find movies automatically
|
|
radarr:
|
|
image: lscr.io/linuxserver/radarr:latest
|
|
container_name: radarr
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- /opt/docker-configs/radarr/config:/config
|
|
- ${MEDIA_SHARE}:/share #Access to the entire share
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 7878:7878
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.radarr.rule=Host(`radarr.homelab`)"
|
|
- "traefik.http.routers.radarr.entrypoints=websecure"
|
|
- "traefik.http.routers.radarr.tls=true"
|
|
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
|
|
restart: unless-stopped
|
|
|
|
#Sonarr - used to find tv shows automatically
|
|
sonarr:
|
|
image: lscr.io/linuxserver/sonarr:latest
|
|
container_name: sonarr
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- /opt/docker-configs/sonarr/config:/config
|
|
- ${MEDIA_SHARE}:/share #Access to the entire share
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8989:8989
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.sonarr.rule=Host(`sonarr.homelab`)"
|
|
- "traefik.http.routers.sonarr.entrypoints=websecure"
|
|
- "traefik.http.routers.sonarr.tls=true"
|
|
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
|
|
restart: unless-stopped
|
|
|
|
#Readarr - Used to download books
|
|
#
|
|
#NOTE: DISABLED - LinuxServer image deprecated and no amd64 support available
|
|
#TODO: Find alternative or wait for upstream fix
|
|
#readarr:
|
|
# image: linuxserver/readarr:develop
|
|
# container_name: readarr
|
|
# environment:
|
|
# - PUID=${PUID}
|
|
# - PGID=${GUID}
|
|
# - TZ=${TZ}
|
|
# volumes:
|
|
# - ${BASE_PATH}/readarr/config:/config
|
|
# - ${MEDIA_SHARE}:/share
|
|
# ports:
|
|
# - 8787:8787
|
|
# restart: unless-stopped
|
|
|
|
#Lidarr - Used to download music
|
|
lidarr:
|
|
image: lscr.io/linuxserver/lidarr:latest
|
|
container_name: lidarr
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- /opt/docker-configs/lidarr/config:/config
|
|
- ${MEDIA_SHARE}:/share
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8686:8686
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.lidarr.rule=Host(`lidarr.homelab`)"
|
|
- "traefik.http.routers.lidarr.entrypoints=websecure"
|
|
- "traefik.http.routers.lidarr.tls=true"
|
|
- "traefik.http.services.lidarr.loadbalancer.server.port=8686"
|
|
restart: unless-stopped
|
|
|
|
#Prowlarr - manages your Sonarr, Radarr and download client
|
|
prowlarr:
|
|
image: lscr.io/linuxserver/prowlarr:latest
|
|
container_name: prowlarr
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- /opt/docker-configs/prowlarr/config:/config
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 9696:9696
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.prowlarr.rule=Host(`prowlarr.homelab`)"
|
|
- "traefik.http.routers.prowlarr.entrypoints=websecure"
|
|
- "traefik.http.routers.prowlarr.tls=true"
|
|
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
|
|
restart: unless-stopped
|
|
|
|
#Autobrr - used to grab torrents using the trackers IRC channel - Increases seeding due to grabbing content before RSS feed
|
|
autobrr:
|
|
container_name: autobrr
|
|
image: ghcr.io/autobrr/autobrr:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
- TZ=${TZ}
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
volumes:
|
|
- ${BASE_PATH}/autobrr/config:/config
|
|
ports:
|
|
- 7474:7474
|
|
|
|
#Seerr - allows users to request media on their own
|
|
#
|
|
#UPDATED: Replaces Overseerr. Seerr is the unified successor to Overseerr and Jellyseerr
|
|
#Supports Plex, Jellyfin, and Emby
|
|
#Note: Using develop tag until stable release is available
|
|
#Migration guide: https://docs.seerr.dev/
|
|
seerr:
|
|
image: ghcr.io/seerr-team/seerr:develop
|
|
container_name: seerr
|
|
environment:
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${BASE_PATH}/seerr/config:/app/config
|
|
- ${MEDIA_SHARE}:/share # Access to validate root folders
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 5055:5055
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.seerr.rule=Host(`pedirfilmes.homelab`)"
|
|
- "traefik.http.routers.seerr.entrypoints=websecure"
|
|
- "traefik.http.routers.seerr.tls=true"
|
|
- "traefik.http.services.seerr.loadbalancer.server.port=5055"
|
|
restart: unless-stopped
|
|
|
|
#Byparr - Used as a proxy server to bypass Cloudflare and DDoS-GUARD protection
|
|
#
|
|
#UPDATED: Replaces Flaresolverr. Byparr is a modern drop-in replacement using Camoufox
|
|
#Uses the same API as Flaresolverr, so no changes needed in Prowlarr config
|
|
#https://github.com/ThePhaseless/Byparr
|
|
byparr:
|
|
image: ghcr.io/thephaseless/byparr:latest
|
|
container_name: byparr
|
|
init: true # Reaps zombie processes from Camoufox/Firefox
|
|
environment:
|
|
- LOG_LEVEL=info
|
|
- TZ=${TZ}
|
|
ports:
|
|
- 8191:8191
|
|
restart: unless-stopped
|
|
|
|
#Qbittorrent - torrenting software
|
|
#
|
|
#All traffic routed through Gluetun VPN container
|
|
#Ports are defined on the gluetun container since qBittorrent uses its network
|
|
qbittorrent:
|
|
image: lscr.io/linuxserver/qbittorrent:latest
|
|
container_name: qbittorrent
|
|
network_mode: "service:gluetun"
|
|
depends_on:
|
|
- gluetun
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
- WEBUI_PORT=8080
|
|
- TORRENTING_PORT=8694
|
|
volumes:
|
|
- ${BASE_PATH}/qbittorrent/config:/config
|
|
- ${MEDIA_SHARE}:/share
|
|
restart: unless-stopped
|
|
|
|
#Tautulli - for plex statistics. Very useful when troubleshooting performance issues
|
|
tautulli:
|
|
image: lscr.io/linuxserver/tautulli:latest
|
|
container_name: tautulli
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${BASE_PATH}/tautulli:/config
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8181:8181
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.tautulli.rule=Host(`tautulli.homelab`)"
|
|
- "traefik.http.routers.tautulli.entrypoints=websecure"
|
|
- "traefik.http.routers.tautulli.tls=true"
|
|
- "traefik.http.services.tautulli.loadbalancer.server.port=8181"
|
|
restart: unless-stopped
|
|
|
|
#Tdarr - to transcode videos from one format to another like x265 or H.265
|
|
#
|
|
#This container requires a decent amount of horse power to run but will save space in the long run
|
|
#Alternative open-source option: Unmanic (https://github.com/Unmanic/unmanic)
|
|
tdarr:
|
|
container_name: tdarr
|
|
image: ghcr.io/haveagitgat/tdarr:latest
|
|
restart: unless-stopped
|
|
ports:
|
|
- 8265:8265 # webUI port
|
|
- 8266:8266 # server port
|
|
environment:
|
|
- TZ=${TZ}
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- UMASK_SET=002
|
|
- nodeName=ServerNode
|
|
- serverIP=0.0.0.0
|
|
- serverPort=8266
|
|
- webUIPort=8265
|
|
- internalNode=true
|
|
- inContainer=true
|
|
- ffmpegVersion=6
|
|
volumes:
|
|
- ${BASE_PATH}/tdarr/server:/app/server
|
|
- ${BASE_PATH}/tdarr/configs:/app/configs
|
|
- ${BASE_PATH}/tdarr/logs:/app/logs
|
|
- ${MEDIA_SHARE}:/media
|
|
- /transcode_cache:/temp
|
|
devices:
|
|
- /dev/dri:/dev/dri #Required for HW transcoding / QuickSync
|
|
|
|
#Membarr - to invite users via discord
|
|
#
|
|
#DISABLED - Using Wizarr instead
|
|
#membarr:
|
|
# container_name: membarr
|
|
# image: yoruio/membarr:latest
|
|
# restart: unless-stopped
|
|
# environment:
|
|
# - token=${MEMBARR_TOKEN}
|
|
# volumes:
|
|
# - ${BASE_PATH}/membarr/config:/app/app/config
|
|
|
|
#Bazarr - for subtitles. Try to use SRT format if you can rather than PGS due to performance issues
|
|
bazarr:
|
|
container_name: bazarr
|
|
image: lscr.io/linuxserver/bazarr:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- /opt/docker-configs/bazarr/config:/config
|
|
- ${MEDIA_SHARE}:/share
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 6767:6767
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.bazarr.rule=Host(`legendas.homelab`)"
|
|
- "traefik.http.routers.bazarr.entrypoints=websecure"
|
|
- "traefik.http.routers.bazarr.tls=true"
|
|
- "traefik.http.services.bazarr.loadbalancer.server.port=6767"
|
|
|
|
#Plex Auto Languages - This switches languages automatically example: watching english show and non english speaks you get subtitle
|
|
#
|
|
#UPDATED: Using JourneyOver fork which is actively maintained
|
|
#Alternative: thesammykins TypeScript rewrite (https://github.com/thesammykins/plex-auto-languages)
|
|
plexautolanguages:
|
|
image: journeyover/plex-auto-languages:latest
|
|
container_name: plex-auto-languages
|
|
environment:
|
|
- PLEX_URL=${PLEX_URL} #This is your local URL example: http://192.168.1.10:32400
|
|
#To find your plex token go to https://app.plex.tv/, go to your library, click on the 3 dots on the bottom right of one of your tv/movie posters
|
|
#Then click "View XML" in the bottom right of that popup, look at the URL of the XML window and find your X-Plex-Token= in the URL (at the very end)
|
|
- PLEX_TOKEN=${PLEX_TOKEN}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${BASE_PATH}/pal/config:/config
|
|
restart: unless-stopped
|
|
|
|
#Cross Seed - used to take torrents from one tracker and seed them on another without leeching first. Increases ratio
|
|
#
|
|
#DISABLED - Requires advanced configuration
|
|
#cross-seed:
|
|
# image: ghcr.io/cross-seed/cross-seed:6
|
|
# container_name: cross-seed
|
|
# environment:
|
|
# - PUID=${PUID}
|
|
# - PGID=${GUID}
|
|
# ports:
|
|
# - "2468:2468"
|
|
# volumes:
|
|
# - ${BASE_PATH}/cross-seed/config:/config
|
|
# - ${BASE_PATH}/qbittorrent/config/qBittorrent/BT_backup:/torrents:ro
|
|
# - ${MEDIA_SHARE}/cross-seed/current-cross-seeds:/cross-seeds
|
|
# command: daemon
|
|
# restart: unless-stopped
|
|
|
|
#Kometa - used to create collections in plex. Example: "Most Popular Movies This Week", "Best of horror", etc.
|
|
#
|
|
#UPDATED: Replaces Plex Meta Manager which was deprecated in April 2024
|
|
#Migration guide: https://kometa.wiki/en/latest/kometa/guides/rebrand/
|
|
kometa:
|
|
image: kometateam/kometa:latest
|
|
container_name: kometa
|
|
environment:
|
|
- TZ=${TZ}
|
|
#- KOMETA_OVERLAYS_ONLY=true #Tells Kometa to run overlays only
|
|
- KOMETA_CONFIG=/config/config.yml
|
|
- KOMETA_TIME=03:00 #Runs daily at 3 AM
|
|
#- KOMETA_RUN=true #Runs Kometa immediately (use for one-off manual runs)
|
|
#- KOMETA_RUN_LIBRARIES=Movies #Tells Kometa to process only a library called "Movies"
|
|
volumes:
|
|
- ${BASE_PATH}/kometa/config:/config
|
|
restart: unless-stopped
|
|
|
|
#Wizarr - Allows you to create a share link that you can send to users to invite them to your media server
|
|
#Supports Plex, Jellyfin, Emby, Audiobookshelf, Romm, Komga and Kavita
|
|
wizarr:
|
|
container_name: wizarr
|
|
image: ghcr.io/wizarrrr/wizarr:latest
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 5690:5690
|
|
volumes:
|
|
- ${BASE_PATH}/wizarr/data/database:/data/database
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.wizarr.rule=Host(`convites.homelab`)"
|
|
- "traefik.http.routers.wizarr.entrypoints=websecure"
|
|
- "traefik.http.routers.wizarr.tls=true"
|
|
- "traefik.http.services.wizarr.loadbalancer.server.port=5690"
|
|
restart: unless-stopped
|
|
|
|
#Dozzle - Used to easily view logs of any container in real time!
|
|
#For historical logs/search, consider Loki + Grafana
|
|
dozzle:
|
|
container_name: dozzle
|
|
image: amir20/dozzle:latest
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 9999:8080
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.dozzle.rule=Host(`logs.homelab`)"
|
|
- "traefik.http.routers.dozzle.entrypoints=websecure"
|
|
- "traefik.http.routers.dozzle.tls=true"
|
|
- "traefik.http.services.dozzle.loadbalancer.server.port=8080"
|
|
restart: unless-stopped
|
|
|
|
#Unpackerr - Used to unzip zipped files
|
|
unpackerr:
|
|
image: golift/unpackerr:latest
|
|
container_name: unpackerr
|
|
volumes:
|
|
# You need at least this one volume mapped so Unpackerr can find your files to extract.
|
|
# Make sure this matches your Starr apps; the folder mount (/downloads or /data) should be identical.
|
|
- ${MEDIA_SHARE}:/share
|
|
#- ${BASE_PATH}/unpackerr/config:/config
|
|
restart: unless-stopped
|
|
user: 1000:1000 #Needs to run as 1000
|
|
# What you see below are defaults for this compose. You only need to modify things specific to your environment.
|
|
# Remove apps and feature configs you do not use or need.
|
|
# ie. Remove all lines that begin with UN_CMDHOOK, UN_WEBHOOK, UN_FOLDER, UN_WEBSERVER, and other apps you do not use.
|
|
environment:
|
|
- UN_START_DELAY=1m
|
|
#- UMASK=002
|
|
- TZ=${TZ}
|
|
#- UN_DEBUG=true
|
|
# Sonarr Config
|
|
- UN_SONARR_0_URL=http://${SERVER_IP}:8989
|
|
- UN_SONARR_0_API_KEY=${SONARR_KEY}
|
|
#- UN_SONARR_0_PATHS_0=/share/downloads/tv
|
|
- UN_SONARR_0_TIMEOUT=10s
|
|
#- UN_SONARR_0_PATHS_0=/share/downloads/tv
|
|
# Radarr Config
|
|
- UN_RADARR_0_URL=http://${SERVER_IP}:7878
|
|
- UN_RADARR_0_API_KEY=${RADARR_KEY}
|
|
#- UN_RADARR_0_PATHS_0=/share/downloads/movies
|
|
- UN_RADARR_0_TIMEOUT=10s
|
|
#- UN_RADARR_0_PATHS_0=/share/downloads/movies
|
|
|
|
# Recyclarr - Used to sync Trash Guides config's to radarr and sonarr
|
|
# Only accessible via command line! No GUI
|
|
# Supports Radarr and Sonarr v4+
|
|
recyclarr:
|
|
image: ghcr.io/recyclarr/recyclarr:latest
|
|
container_name: recyclarr
|
|
user: ${PUID}:${GUID}
|
|
volumes:
|
|
- ${BASE_PATH}/recyclarr/config:/config
|
|
environment:
|
|
- TZ=${TZ}
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# MEDIA DOWNLOADS
|
|
# ============================================
|
|
|
|
#MeTube - Web UI for yt-dlp (YouTube and 1000+ sites)
|
|
#
|
|
#Browser extensions available for one-click downloads
|
|
#https://github.com/alexta69/metube
|
|
metube:
|
|
image: ghcr.io/alexta69/metube:latest
|
|
container_name: metube
|
|
environment:
|
|
- UID=${PUID}
|
|
- GID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${MEDIA_SHARE}/downloads:/downloads
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8081:8081
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.metube.rule=Host(`metube.homelab`)"
|
|
- "traefik.http.routers.metube.entrypoints=websecure"
|
|
- "traefik.http.routers.metube.tls=true"
|
|
- "traefik.http.services.metube.loadbalancer.server.port=8081"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# DASHBOARD
|
|
# ============================================
|
|
|
|
#Homepage - Modern dashboard for all your services
|
|
#
|
|
#https://gethomepage.dev/
|
|
homepage:
|
|
image: ghcr.io/gethomepage/homepage:latest
|
|
container_name: homepage
|
|
group_add:
|
|
- "999" # docker group GID for socket access
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
- HOMEPAGE_ALLOWED_HOSTS=10.10.11.201:3000,localhost:3000,homelab:3000,dashboard.homelab
|
|
volumes:
|
|
- ${BASE_PATH}/homepage/config:/app/config
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- /mnt/truenas/movies:/mnt/truenas/movies:ro
|
|
- /mnt/truenas/backup-appdata:/mnt/truenas/backup-appdata:ro
|
|
- /mnt/appdata/xxx:/mnt/appdata/xxx:ro
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 3000:3000
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.homepage.rule=Host(`dashboard.homelab`)"
|
|
- "traefik.http.routers.homepage.entrypoints=websecure"
|
|
- "traefik.http.routers.homepage.tls=true"
|
|
- "traefik.http.services.homepage.loadbalancer.server.port=3000"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# SOURCE CODE MANAGEMENT
|
|
# ============================================
|
|
|
|
#Forgejo - Self-hosted Git service (lightweight Gitea fork)
|
|
#
|
|
#Community-driven fork focused on sustainability and independence
|
|
#First run: Access http://SERVER_IP:4000 to complete setup
|
|
#SSH access available on port 2222
|
|
#https://forgejo.org/
|
|
forgejo:
|
|
image: codeberg.org/forgejo/forgejo:13
|
|
container_name: forgejo
|
|
environment:
|
|
- USER_UID=${PUID}
|
|
- USER_GID=${GUID}
|
|
- TZ=${TZ}
|
|
- FORGEJO__database__DB_TYPE=sqlite3
|
|
- FORGEJO__server__ROOT_URL=http://${SERVER_IP}:4000/
|
|
- FORGEJO__server__SSH_PORT=2222
|
|
- FORGEJO__server__SSH_LISTEN_PORT=22
|
|
volumes:
|
|
- ${BASE_PATH}/forgejo/data:/data
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 4000:3000 # Web UI
|
|
- 2222:22 # SSH for Git
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.forgejo.rule=Host(`git.homelab`)"
|
|
- "traefik.http.routers.forgejo.entrypoints=websecure"
|
|
- "traefik.http.routers.forgejo.tls=true"
|
|
- "traefik.http.services.forgejo.loadbalancer.server.port=3000"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# ADULT CONTENT MANAGEMENT
|
|
# ============================================
|
|
|
|
#Whisparr - Automated adult content management (part of the Servarr family)
|
|
#
|
|
#Works like Radarr/Sonarr but for adult content
|
|
#Integrates with Prowlarr for indexers
|
|
#https://wiki.servarr.com/whisparr
|
|
whisparr:
|
|
image: ghcr.io/hotio/whisparr:latest
|
|
container_name: whisparr
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- /opt/docker-configs/whisparr/config:/config
|
|
- ${MEDIA_SHARE}:/share # Full share access for hardlinking (downloads)
|
|
- /mnt/appdata/xxx:/share/xxx # Dedicated xxx share mapped to same internal path
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 6969:6969
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.whisparr.rule=Host(`whisparr.homelab`)"
|
|
- "traefik.http.routers.whisparr.entrypoints=websecure"
|
|
- "traefik.http.routers.whisparr.tls=true"
|
|
- "traefik.http.services.whisparr.loadbalancer.server.port=6969"
|
|
restart: unless-stopped
|
|
|
|
#Stash - Adult media organizer with metadata scraping
|
|
#
|
|
#Features: metadata scraping, scene detection, performer identification,
|
|
#tagging system, duplicate detection, web-based streaming
|
|
#https://stashapp.cc/
|
|
stash:
|
|
image: stashapp/stash:latest
|
|
container_name: stash
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
- STASH_STASH=/data/
|
|
- STASH_GENERATED=/generated/
|
|
- STASH_METADATA=/metadata/
|
|
- STASH_CACHE=/cache/
|
|
volumes:
|
|
- /opt/docker-configs/stash/config:/root/.stash
|
|
- /opt/docker-configs/stash/generated:/generated
|
|
- /opt/docker-configs/stash/metadata:/metadata
|
|
- /opt/docker-configs/stash/cache:/cache
|
|
- /opt/docker-configs/stash/blobs:/blobs
|
|
- /mnt/appdata/xxx:/data # Adult content library (dedicated NFS share)
|
|
devices:
|
|
- /dev/dri:/dev/dri # Hardware transcoding (VAAPI)
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 9998:9999
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.stash.rule=Host(`stash.homelab`)"
|
|
- "traefik.http.routers.stash.entrypoints=websecure"
|
|
- "traefik.http.routers.stash.tls=true"
|
|
- "traefik.http.services.stash.loadbalancer.server.port=9999"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# SECURITY & AUTHENTICATION
|
|
# ============================================
|
|
|
|
#Docker Socket Proxy - Secure proxy for Docker socket access
|
|
#
|
|
#Prevents containers from getting root-equivalent access to the host
|
|
#Traefik and Dozzle connect to this instead of the raw socket
|
|
#https://github.com/Tecnativa/docker-socket-proxy
|
|
docker-socket-proxy:
|
|
image: tecnativa/docker-socket-proxy:latest
|
|
container_name: docker-socket-proxy
|
|
environment:
|
|
- CONTAINERS=1
|
|
- SERVICES=1
|
|
- TASKS=1
|
|
- NETWORKS=1
|
|
- IMAGES=1
|
|
- INFO=1
|
|
- EVENTS=1
|
|
- PING=1
|
|
- VERSION=1
|
|
- POST=0 # Read-only
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 2375:2375
|
|
privileged: true
|
|
restart: unless-stopped
|
|
|
|
#Authelia - Single Sign-On and 2FA portal
|
|
#
|
|
#Protects all services behind Traefik with a unified login page
|
|
#Supports TOTP, WebAuthn/FIDO2, and Duo push
|
|
#https://www.authelia.com/
|
|
authelia:
|
|
image: authelia/authelia:4.37
|
|
container_name: authelia
|
|
environment:
|
|
- TZ=${TZ}
|
|
- AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET}
|
|
- AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET}
|
|
- AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY}
|
|
volumes:
|
|
- ${BASE_PATH}/authelia/config:/config
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 9091:9091
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.authelia.rule=Host(`auth.homelab`)"
|
|
- "traefik.http.routers.authelia.entrypoints=websecure"
|
|
- "traefik.http.routers.authelia.tls=true"
|
|
- "traefik.http.services.authelia.loadbalancer.server.port=9091"
|
|
restart: unless-stopped
|
|
|
|
#Vaultwarden - Lightweight Bitwarden-compatible password manager
|
|
#
|
|
#Use official Bitwarden clients (browser, mobile, desktop)
|
|
#Requires HTTPS for browser extensions (use via Traefik)
|
|
#https://github.com/dani-garcia/vaultwarden
|
|
vaultwarden:
|
|
image: vaultwarden/server:latest
|
|
container_name: vaultwarden
|
|
environment:
|
|
- TZ=${TZ}
|
|
- DOMAIN=https://vault.homelab
|
|
- SIGNUPS_ALLOWED=true
|
|
volumes:
|
|
- ${BASE_PATH}/vaultwarden/data:/data
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8222:80
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.vaultwarden.rule=Host(`vault.homelab`)"
|
|
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
|
|
- "traefik.http.routers.vaultwarden.tls=true"
|
|
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# VPN & NETWORKING
|
|
# ============================================
|
|
|
|
#Gluetun - VPN container for routing traffic through WireGuard/OpenVPN
|
|
#
|
|
#qBittorrent routes all traffic through this container
|
|
#Built-in kill switch prevents leaks
|
|
#https://github.com/qdm12/gluetun
|
|
gluetun:
|
|
image: qmcgaw/gluetun:latest
|
|
container_name: gluetun
|
|
cap_add:
|
|
- NET_ADMIN
|
|
devices:
|
|
- /dev/net/tun:/dev/net/tun
|
|
environment:
|
|
- VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
|
- VPN_TYPE=${VPN_TYPE}
|
|
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
|
|
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
|
|
- SERVER_COUNTRIES=Ireland
|
|
- TZ=${TZ}
|
|
- FIREWALL_OUTBOUND_SUBNETS=10.10.11.0/24,172.16.0.0/12
|
|
- HTTPPROXY=off
|
|
- SHADOWSOCKS=off
|
|
- DOT=off
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8000:8000 # Gluetun control server
|
|
- 8080:8080 # qBittorrent WebUI
|
|
- 8694:8694 # qBittorrent torrenting
|
|
- 8694:8694/udp
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.qbittorrent.rule=Host(`downloads.homelab`)"
|
|
- "traefik.http.routers.qbittorrent.entrypoints=websecure"
|
|
- "traefik.http.routers.qbittorrent.tls=true"
|
|
- "traefik.http.services.qbittorrent.loadbalancer.server.port=8080"
|
|
restart: unless-stopped
|
|
|
|
#WireGuard Easy - VPN server with web UI
|
|
#
|
|
#Secure remote access to your homelab from anywhere
|
|
#QR codes for easy mobile setup
|
|
#https://github.com/wg-easy/wg-easy
|
|
wg-easy:
|
|
image: ghcr.io/wg-easy/wg-easy:latest
|
|
container_name: wg-easy
|
|
environment:
|
|
- LANGUAGE=en
|
|
- WG_HOST=${SERVER_IP}
|
|
- PASSWORD_HASH=$$2a$$12$$placeholder
|
|
- WG_DEFAULT_DNS=10.10.11.201
|
|
volumes:
|
|
- ${BASE_PATH}/wg-easy/config:/etc/wireguard
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 51820:51820/udp
|
|
- 51821:51821/tcp
|
|
cap_add:
|
|
- NET_ADMIN
|
|
- SYS_MODULE
|
|
sysctls:
|
|
- net.ipv4.conf.all.src_valid_mark=1
|
|
- net.ipv4.ip_forward=1
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.wg-easy.rule=Host(`vpn.homelab`)"
|
|
- "traefik.http.routers.wg-easy.entrypoints=websecure"
|
|
- "traefik.http.routers.wg-easy.tls=true"
|
|
- "traefik.http.services.wg-easy.loadbalancer.server.port=51821"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# MONITORING & MAINTENANCE
|
|
# ============================================
|
|
|
|
#Gotify - Self-hosted push notification server
|
|
#
|
|
#Unified notification endpoint for all services
|
|
#Android app available for push notifications
|
|
#https://gotify.net/
|
|
gotify:
|
|
image: gotify/server:latest
|
|
container_name: gotify
|
|
environment:
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${BASE_PATH}/gotify/data:/app/data
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8083:80
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.gotify.rule=Host(`notificacoes.homelab`)"
|
|
- "traefik.http.routers.gotify.entrypoints=websecure"
|
|
- "traefik.http.routers.gotify.tls=true"
|
|
- "traefik.http.services.gotify.loadbalancer.server.port=80"
|
|
restart: unless-stopped
|
|
|
|
#Maintainerr - Automated Plex media cleanup
|
|
#
|
|
#Creates rules to remove unwatched content with grace periods
|
|
#Shows "Leaving Soon" collection on Plex
|
|
#https://github.com/jorenn92/Maintainerr
|
|
maintainerr:
|
|
image: ghcr.io/jorenn92/maintainerr:latest
|
|
container_name: maintainerr
|
|
environment:
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${BASE_PATH}/maintainerr/data:/opt/data
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 6246:6246
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.maintainerr.rule=Host(`maintainerr.homelab`)"
|
|
- "traefik.http.routers.maintainerr.entrypoints=websecure"
|
|
- "traefik.http.routers.maintainerr.tls=true"
|
|
- "traefik.http.services.maintainerr.loadbalancer.server.port=6246"
|
|
restart: unless-stopped
|
|
|
|
#Speedtest Tracker - Automated internet speed testing with history
|
|
#
|
|
#Scheduled tests with graphs and trend analysis
|
|
#https://github.com/alexjustesen/speedtest-tracker
|
|
speedtest-tracker:
|
|
image: lscr.io/linuxserver/speedtest-tracker:latest
|
|
container_name: speedtest-tracker
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
- DB_CONNECTION=sqlite
|
|
- APP_KEY=base64:V1NVcVVIVmk5cDVnelUwMzNOQjVRM1R2NEZYSmpGZGg=
|
|
volumes:
|
|
- ${BASE_PATH}/speedtest-tracker/config:/config
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8084:80
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.speedtest.rule=Host(`speedtest.homelab`)"
|
|
- "traefik.http.routers.speedtest.entrypoints=websecure"
|
|
- "traefik.http.routers.speedtest.tls=true"
|
|
- "traefik.http.services.speedtest.loadbalancer.server.port=80"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# PRODUCTIVITY & TOOLS
|
|
# ============================================
|
|
|
|
#IT-Tools - Collection of 100+ developer utilities
|
|
#
|
|
#JSON/YAML converters, hash generators, Base64, JWT decoder, etc.
|
|
#https://github.com/CorentinTh/it-tools
|
|
it-tools:
|
|
image: corentinth/it-tools:latest
|
|
container_name: it-tools
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 8085:80
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.it-tools.rule=Host(`tools.homelab`)"
|
|
- "traefik.http.routers.it-tools.entrypoints=websecure"
|
|
- "traefik.http.routers.it-tools.tls=true"
|
|
- "traefik.http.services.it-tools.loadbalancer.server.port=80"
|
|
restart: unless-stopped
|
|
|
|
#Mealie - Recipe manager and meal planner
|
|
#
|
|
#Import recipes by URL, strips ads and blog posts
|
|
#Shopping lists and household management
|
|
#https://mealie.io/
|
|
mealie:
|
|
image: ghcr.io/mealie-recipes/mealie:latest
|
|
container_name: mealie
|
|
environment:
|
|
- PUID=${PUID}
|
|
- PGID=${GUID}
|
|
- TZ=${TZ}
|
|
- ALLOW_SIGNUP=false
|
|
- MAX_WORKERS=1
|
|
- WEB_CONCURRENCY=1
|
|
- BASE_URL=http://${SERVER_IP}:9925
|
|
volumes:
|
|
- ${BASE_PATH}/mealie/data:/app/data
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 9925:9000
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.mealie.rule=Host(`receitas.homelab`)"
|
|
- "traefik.http.routers.mealie.entrypoints=websecure"
|
|
- "traefik.http.routers.mealie.tls=true"
|
|
- "traefik.http.services.mealie.loadbalancer.server.port=9000"
|
|
restart: unless-stopped
|
|
|
|
# ============================================
|
|
# BACKUP & RECOVERY
|
|
# ============================================
|
|
|
|
#Backrest - Web UI for restic backups
|
|
#
|
|
#Backs up all homelab configs (NFS + local SQLite DBs) to TrueNAS
|
|
#First run: Access https://backups.homelab to create admin account
|
|
#https://github.com/garethgeorge/backrest
|
|
backrest:
|
|
image: ghcr.io/garethgeorge/backrest:latest
|
|
container_name: backrest
|
|
environment:
|
|
- TZ=${TZ}
|
|
- BACKREST_DATA=/data
|
|
- BACKREST_CONFIG=/config/config.json
|
|
- XDG_CACHE_HOME=/cache
|
|
volumes:
|
|
- ${BASE_PATH}/backrest/data:/data
|
|
- ${BASE_PATH}/backrest/config:/config
|
|
- ${BASE_PATH}/backrest/cache:/cache
|
|
- /mnt/truenas/config:/sources/truenas-config:ro
|
|
- /opt/docker-configs:/sources/local-configs:ro
|
|
- ${DOCKER_PATH}:/sources/docker-persistent:ro
|
|
- /mnt/truenas/backup-appdata:/repos/truenas-backups
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 9898:9898
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.backrest.rule=Host(`backups.homelab`)"
|
|
- "traefik.http.routers.backrest.entrypoints=websecure"
|
|
- "traefik.http.routers.backrest.tls=true"
|
|
- "traefik.http.services.backrest.loadbalancer.server.port=9898"
|
|
restart: unless-stopped
|
|
|
|
#Reddit Counter - Counts media files in reddit collection
|
|
reddit-counter:
|
|
image: python:3-alpine
|
|
container_name: reddit-counter
|
|
command: python /app/serve.py
|
|
volumes:
|
|
- /opt/docker-configs/reddit-counter/serve.py:/app/serve.py:ro
|
|
- /mnt/appdata/xxx:/data:ro
|
|
ports:
|
|
- 8086:8080
|
|
restart: unless-stopped
|
|
|
|
#Actual Budget - Personal finance and budgeting (YNAB alternative)
|
|
#
|
|
#Local-first architecture, works offline, syncs across devices
|
|
#https://actualbudget.org/
|
|
actual-budget:
|
|
image: actualbudget/actual-server:latest
|
|
container_name: actual-budget
|
|
environment:
|
|
- TZ=${TZ}
|
|
volumes:
|
|
- ${BASE_PATH}/actual-budget/data:/data
|
|
networks:
|
|
- proxy
|
|
ports:
|
|
- 5006:5006
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.actual.rule=Host(`orcamento.homelab`)"
|
|
- "traefik.http.routers.actual.entrypoints=websecure"
|
|
- "traefik.http.routers.actual.tls=true"
|
|
- "traefik.http.services.actual.loadbalancer.server.port=5006"
|
|
restart: unless-stopped
|