From f9a34fe9c773df0758ed9bf5c0902bdccc8ea1a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:13:55 +0000 Subject: [PATCH] Implement Dockge structure with Traefik, Authelia, DuckDNS, and Gluetun VPN - Update AI copilot instructions for /opt/stacks structure and automated config management - Replace Nginx Proxy Manager with Traefik (file-based configuration for AI) - Add Authelia for SSO with bypass rules for Jellyfin/Plex apps - Add DuckDNS for dynamic DNS with Let's Encrypt integration - Add Gluetun VPN with Surfshark (WireGuard) for secure downloads - Update all services to use /opt/stacks paths instead of local directories - Add Traefik labels to all services for automatic routing - Configure qBittorrent to route through Gluetun VPN - Update .env.example with all new required variables - Create configuration templates for Traefik and Authelia - Add comprehensive Dockge deployment guide Co-authored-by: kelinfoxy <67766943+kelinfoxy@users.noreply.github.com> --- .env.example | 38 ++- .github/copilot-instructions.md | 255 +++++++++++++-- config-templates/authelia/configuration.yml | 93 ++++++ config-templates/authelia/users_database.yml | 20 ++ config-templates/traefik/dynamic/routes.yml | 31 ++ config-templates/traefik/traefik.yml | 58 ++++ docker-compose/README-dockge.md | 324 +++++++++++++++++++ docker-compose/authelia.yml | 39 +++ docker-compose/duckdns.yml | 21 ++ docker-compose/gluetun.yml | 81 +++++ docker-compose/infrastructure.yml | 74 +++-- docker-compose/media.yml | 117 ++++--- docker-compose/traefik.yml | 43 +++ 13 files changed, 1082 insertions(+), 112 deletions(-) create mode 100644 config-templates/authelia/configuration.yml create mode 100644 config-templates/authelia/users_database.yml create mode 100644 config-templates/traefik/dynamic/routes.yml create mode 100644 config-templates/traefik/traefik.yml create mode 100644 docker-compose/README-dockge.md create mode 100644 docker-compose/authelia.yml create mode 100644 docker-compose/duckdns.yml create mode 100644 docker-compose/gluetun.yml create mode 100644 docker-compose/traefik.yml diff --git a/.env.example b/.env.example index ee0642c..9f171bb 100644 --- a/.env.example +++ b/.env.example @@ -12,12 +12,41 @@ TZ=America/New_York # Server IP address SERVER_IP=192.168.1.100 +# Domain Configuration +DOMAIN=yourdomain.duckdns.org # Your DuckDNS domain + # Directory Paths -USERDIR=/home/username/homelab -MEDIADIR=/mnt/media -DOWNLOADDIR=/mnt/downloads +USERDIR=/opt/stacks +MEDIADIR=/mnt/media # Large media files on separate drive +DOWNLOADDIR=/mnt/downloads # Downloads on separate drive PROJECTDIR=/home/username/projects +# DuckDNS Configuration +DUCKDNS_TOKEN=your-duckdns-token +DUCKDNS_SUBDOMAINS=yourdomain # Without .duckdns.org + +# Let's Encrypt / ACME +ACME_EMAIL=your-email@example.com + +# Authelia Secrets (generate with: openssl rand -hex 64) +AUTHELIA_JWT_SECRET=your-jwt-secret-here-64-chars +AUTHELIA_SESSION_SECRET=your-session-secret-here-64-chars +AUTHELIA_STORAGE_ENCRYPTION_KEY=your-encryption-key-here-64-chars + +# SMTP for Authelia Notifications (optional) +SMTP_USERNAME=your-email@example.com +SMTP_PASSWORD=your-smtp-password + +# VPN Configuration (Surfshark) +# Get WireGuard details from Surfshark dashboard +SURFSHARK_PRIVATE_KEY=your-wireguard-private-key +SURFSHARK_ADDRESSES=10.14.0.2/16 +VPN_COUNTRY=Netherlands # Preferred VPN server location + +# Alternative: OpenVPN credentials (if not using WireGuard) +# SURFSHARK_USERNAME=your-surfshark-username +# SURFSHARK_PASSWORD=your-surfshark-password + # Plex Configuration PLEX_CLAIM=claim-xxxxxxxxxx @@ -45,4 +74,7 @@ PIHOLE_PASSWORD=changeme # Watchtower Notifications (optional) # WATCHTOWER_NOTIFICATION_URL= +# Cloudflare API (optional, for DNS challenge) +# CF_DNS_API_TOKEN=your-cloudflare-api-token + # Add your own variables below diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1186bc5..5712a42 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,26 +1,45 @@ # AI Homelab Management Assistant -You are an AI assistant specialized in managing Docker-based homelab infrastructure. Your role is to help users create, modify, and manage Docker services while maintaining consistency across the entire server stack. +You are an AI assistant specialized in managing Docker-based homelab infrastructure using Dockge. Your role is to help users create, modify, and manage Docker services while maintaining consistency across the entire server stack. ## Core Principles -### 1. Docker Compose First +### 1. Dockge and Docker Compose First - **ALWAYS** use Docker Compose stacks for persistent services +- Store all compose files in `/opt/stacks/stack-name/` directories - Only use `docker run` for temporary containers (e.g., testing nvidia-container-toolkit functionality) -- Maintain all services in organized docker-compose.yml files +- Maintain all services in organized docker-compose.yml files within their stack folders -### 2. Consistency is Key +### 2. File Structure and Storage +- **Base Path**: All stacks are stored in `/opt/stacks/` +- **Bind Mounts**: Default to `/opt/stacks/stack-name/` for configuration files +- **Large Data**: Suggest using separate mounted drives for: + - Media files (movies, TV shows, music) - typically `/mnt/media` + - Downloads - typically `/mnt/downloads` + - Database data files that grow large + - Backup storage + - Any data that may exceed 50GB or grow continuously +- **Named Volumes**: Use Docker named volumes for smaller application data + +### 3. Consistency is Key - Keep consistent naming conventions across all compose files - Use the same network naming patterns - Maintain uniform volume mount structures - Apply consistent environment variable patterns -### 3. Stack-Aware Changes +### 4. Stack-Aware Changes - Before making changes, consider the impact on the entire server stack - Check for service dependencies (networks, volumes, other services) - Ensure changes don't break existing integrations - Validate that port assignments don't conflict +### 5. Automated Configuration Management +- Configure all services via configuration files, not web UIs +- Traefik routes configured via Docker labels +- Authelia rules configured via YAML files +- Enable AI to manage and update configurations automatically +- Maintain homelab functionality through code, not manual UI clicks + ## Creating a New Docker Service When creating a new service, follow these steps: @@ -46,10 +65,12 @@ When creating a new service, follow these steps: networks: - homelab-network # Use shared networks ports: - - "host_port:container_port" # Document port purpose + - "host_port:container_port" # Document port purpose (if not using Traefik) volumes: - - ./config/service-name:/config # Config in local directory + - /opt/stacks/stack-name/config:/config # Config in stack directory - service-data:/data # Named volumes for persistent data + # For large data, use separate mount: + # - /mnt/media:/media # Large media files on separate drive environment: - PUID=1000 # Standard user/group IDs - PGID=1000 @@ -57,6 +78,13 @@ When creating a new service, follow these steps: labels: - "homelab.category=category-name" # For organization - "homelab.description=Service description" + # Traefik labels (if using Traefik): + # - "traefik.enable=true" + # - "traefik.http.routers.service-name.rule=Host(`service.domain.com`)" + # - "traefik.http.routers.service-name.entrypoints=websecure" + # - "traefik.http.routers.service-name.tls.certresolver=letsencrypt" + # Authelia middleware (if SSO required): + # - "traefik.http.routers.service-name.middlewares=authelia@docker" volumes: service-data: @@ -213,41 +241,220 @@ environment: ## File Organization ``` -/home/user/homelab/ -├── docker-compose/ -│ ├── media.yml # Media server services -│ ├── monitoring.yml # Monitoring stack -│ ├── development.yml # Dev tools -│ └── infrastructure.yml # Core services -├── config/ -│ ├── service1/ -│ ├── service2/ -│ └── ... -├── data/ # Bind mount data -│ └── ... -├── .env # Global secrets (gitignored) -└── README.md # Stack documentation +/opt/stacks/ +├── stack-name/ +│ ├── docker-compose.yml # Stack definition +│ ├── config/ # Service configurations +│ ├── .env # Stack-specific secrets +│ └── README.md # Stack documentation +├── traefik/ +│ ├── docker-compose.yml +│ ├── traefik.yml # Traefik static config +│ ├── dynamic/ # Dynamic configuration +│ │ └── routes.yml # Route definitions +│ ├── acme.json # Let's Encrypt certificates +│ └── .env +├── authelia/ +│ ├── docker-compose.yml +│ ├── configuration.yml # Authelia config +│ ├── users_database.yml # User definitions +│ └── .env +├── gluetun/ +│ ├── docker-compose.yml +│ └── .env # VPN credentials +└── duckdns/ + ├── docker-compose.yml + └── .env # DuckDNS token ``` +## VPN Integration with Gluetun + +### When to Use VPN +- Download clients (qBittorrent, SABnzbd, etc.) +- Services that need to hide their origin IP +- Services accessing geo-restricted content + +### Gluetun Configuration +- **Default VPN**: Surfshark +- Services connect through Gluetun's network namespace +- Use `network_mode: "service:gluetun"` for VPN routing +- Access via Gluetun's ports: map ports in Gluetun service + +**Example:** +```yaml +services: + gluetun: + image: qmcgaw/gluetun:latest + container_name: gluetun + cap_add: + - NET_ADMIN + environment: + - VPN_SERVICE_PROVIDER=surfshark + - VPN_TYPE=wireguard + - WIREGUARD_PRIVATE_KEY=${SURFSHARK_PRIVATE_KEY} + - WIREGUARD_ADDRESSES=${SURFSHARK_ADDRESSES} + - SERVER_COUNTRIES=Netherlands + ports: + - 8080:8080 # qBittorrent web UI + - 6881:6881 # qBittorrent ports + + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + network_mode: "service:gluetun" # Route through VPN + depends_on: + - gluetun + volumes: + - /opt/stacks/qbittorrent/config:/config + - /mnt/downloads:/downloads +``` + +## SSO with Authelia + +### Authentication Strategy +- **Protected Services**: Most web UIs (require SSO login) +- **Bypass Services**: Apps that need direct access (Jellyfin, Plex, mobile apps) +- **API Endpoints**: Configure bypass rules for API access + +### Authelia Configuration +- Users defined in `users_database.yml` +- Access rules in `configuration.yml` +- Integrate with Traefik via middleware + +### Services Requiring Authelia +- Monitoring dashboards (Grafana, Prometheus, etc.) +- Admin panels (Portainer, etc.) +- Download clients web UIs +- Development tools +- Any service with sensitive data + +### Services Bypassing Authelia +- Jellyfin (for app access - Roku, Fire TV, mobile apps) +- Plex (for app access) +- Home Assistant (has its own auth) +- Services with API-only access +- Public-facing services (if any) + +**Example Traefik Labels with Authelia:** +```yaml +labels: + - "traefik.enable=true" + - "traefik.http.routers.sonarr.rule=Host(`sonarr.${DOMAIN}`)" + - "traefik.http.routers.sonarr.entrypoints=websecure" + - "traefik.http.routers.sonarr.tls.certresolver=letsencrypt" + - "traefik.http.routers.sonarr.middlewares=authelia@docker" # SSO enabled +``` + +**Example Bypassing Authelia (Jellyfin):** +```yaml +labels: + - "traefik.enable=true" + - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.${DOMAIN}`)" + - "traefik.http.routers.jellyfin.entrypoints=websecure" + - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" + # No authelia middleware - direct access for apps +``` + +## Traefik Reverse Proxy + +### Why Traefik Instead of Nginx Proxy Manager +- **File-based configuration**: AI can modify YAML files +- **Docker label integration**: Automatic service discovery +- **No web UI dependency**: Fully automated management +- **Let's Encrypt automation**: Automatic SSL certificate management +- **Dynamic configuration**: Changes without restarts + +### Traefik Configuration Pattern +1. **Static config** (`traefik.yml`): Core settings, entry points, certificate resolvers +2. **Dynamic config** (Docker labels): Per-service routing rules +3. **File provider**: Additional route definitions in `dynamic/` directory + +### Managing Routes via AI +- Traefik routes defined in Docker labels +- AI can read compose files and add/modify labels +- Automatic service discovery when containers start +- Update routes by modifying compose files and redeploying + +**Example Service with Traefik:** +```yaml +services: + service-name: + image: service:latest + container_name: service-name + networks: + - traefik-network + labels: + - "traefik.enable=true" + - "traefik.http.routers.service-name.rule=Host(`service.${DOMAIN}`)" + - "traefik.http.routers.service-name.entrypoints=websecure" + - "traefik.http.routers.service-name.tls.certresolver=letsencrypt" + - "traefik.http.routers.service-name.middlewares=authelia@docker" + - "traefik.http.services.service-name.loadbalancer.server.port=8080" +``` + +## DuckDNS for Dynamic DNS + +### Purpose +- Provides dynamic DNS for home IP addresses +- Integrates with Let's Encrypt for SSL certificates +- Updates automatically when IP changes + +### Configuration +- Single container updates your domain periodically +- Works with Traefik's Let's Encrypt resolver +- Set up once and forget + +## Automated Homelab Management + +### AI's Role in Maintenance +1. **Service Addition**: Create compose files with proper Traefik labels +2. **Route Management**: Update labels to modify proxy routes +3. **SSL Certificates**: Traefik handles automatically via Let's Encrypt +4. **SSO Configuration**: Add/remove authelia middleware as needed +5. **VPN Routing**: Configure services to use Gluetun when required +6. **Monitoring**: Ensure all services are properly configured + +### Configuration Files AI Can Manage +- `docker-compose.yml` files for all stacks +- `traefik/dynamic/routes.yml` for custom routes +- `authelia/configuration.yml` for access rules +- Environment variables in `.env` files +- Service-specific config files in `/opt/stacks/stack-name/config/` + +### What AI Should Monitor +- Port conflicts +- Network connectivity +- Certificate expiration (Traefik handles renewal) +- Service health +- VPN connection status +- Authentication bypass requirements + ## Safety Checks Before deploying any changes: - [ ] YAML syntax is valid - [ ] Ports don't conflict with existing services - [ ] Networks exist or are defined -- [ ] Volume paths are correct +- [ ] Volume paths are correct (use /opt/stacks/ or /mnt/ for large data) - [ ] Environment variables are set - [ ] No secrets in compose files - [ ] Service dependencies are met - [ ] Backup of current configuration exists +- [ ] Traefik labels are correct for routing +- [ ] Authelia middleware applied appropriately +- [ ] VPN routing configured if needed ## Remember - **Think before you act**: Consider the entire stack - **Be consistent**: Follow established patterns +- **Use /opt/stacks/**: All compose files go in stack directories +- **Large data on /mnt/**: Media and downloads go on separate drives +- **Configure via files**: Traefik labels, Authelia YAML, not web UIs - **Document everything**: Future you will thank you - **Test safely**: Use temporary containers first - **Back up first**: Always have a rollback plan -- **Security matters**: Keep secrets secret, update regularly +- **Security matters**: Use Authelia SSO, keep secrets in .env files +- **VPN when needed**: Route download clients through Gluetun -When a user asks you to create or modify a Docker service, follow these guidelines carefully, ask clarifying questions if needed, and always prioritize the stability and consistency of the entire homelab infrastructure. +When a user asks you to create or modify a Docker service, follow these guidelines carefully, ask clarifying questions if needed, and always prioritize the stability, security, and consistency of the entire homelab infrastructure. diff --git a/config-templates/authelia/configuration.yml b/config-templates/authelia/configuration.yml new file mode 100644 index 0000000..6dc44b3 --- /dev/null +++ b/config-templates/authelia/configuration.yml @@ -0,0 +1,93 @@ +# Authelia Configuration +# Copy to /opt/stacks/authelia/configuration.yml + +server: + host: 0.0.0.0 + port: 9091 + +log: + level: info + +theme: dark + +jwt_secret: ${AUTHELIA_JWT_SECRET} + +default_redirection_url: https://auth.${DOMAIN} + +totp: + issuer: ${DOMAIN} + period: 30 + skew: 1 + +authentication_backend: + file: + path: /config/users_database.yml + password: + algorithm: argon2id + iterations: 1 + key_length: 32 + salt_length: 16 + memory: 1024 + parallelism: 8 + +access_control: + default_policy: deny + + rules: + # Bypass Authelia for Jellyfin (allow app access) + - domain: jellyfin.${DOMAIN} + policy: bypass + + # Bypass for Plex (allow app access) + - domain: plex.${DOMAIN} + policy: bypass + + # Bypass for Home Assistant (has its own auth) + - domain: ha.${DOMAIN} + policy: bypass + + # Protected: All other services require authentication + - domain: "*.${DOMAIN}" + policy: one_factor + + # Two-factor for admin services (optional) + # - domain: + # - "admin.${DOMAIN}" + # - "portainer.${DOMAIN}" + # policy: two_factor + +session: + name: authelia_session + secret: ${AUTHELIA_SESSION_SECRET} + expiration: 1h + inactivity: 5m + remember_me_duration: 1M + domain: ${DOMAIN} + +regulation: + max_retries: 3 + find_time: 2m + ban_time: 5m + +storage: + encryption_key: ${AUTHELIA_STORAGE_ENCRYPTION_KEY} + local: + path: /config/db.sqlite3 + +notifier: + # File-based notifications (for testing) + filesystem: + filename: /config/notification.txt + + # SMTP notifications (recommended for production) + # smtp: + # host: smtp.gmail.com + # port: 587 + # username: ${SMTP_USERNAME} + # password: ${AUTHELIA_NOTIFIER_SMTP_PASSWORD} + # sender: authelia@${DOMAIN} + # identifier: localhost + # subject: "[Authelia] {title}" + # startup_check_address: test@authelia.com + # disable_require_tls: false + # disable_html_emails: false diff --git a/config-templates/authelia/users_database.yml b/config-templates/authelia/users_database.yml new file mode 100644 index 0000000..dec2f75 --- /dev/null +++ b/config-templates/authelia/users_database.yml @@ -0,0 +1,20 @@ +# Authelia Users Database +# Copy to /opt/stacks/authelia/users_database.yml +# Generate password hashes with: docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' + +users: + admin: + displayname: "Admin User" + password: "$argon2id$v=19$m=65536,t=3,p=4$CHANGEME" # Replace with your hashed password + email: admin@example.com + groups: + - admins + - users + + # Example: Additional user + # user1: + # displayname: "User One" + # password: "$argon2id$v=19$m=65536,t=3,p=4$CHANGEME" + # email: user1@example.com + # groups: + # - users diff --git a/config-templates/traefik/dynamic/routes.yml b/config-templates/traefik/dynamic/routes.yml new file mode 100644 index 0000000..cdaf10e --- /dev/null +++ b/config-templates/traefik/dynamic/routes.yml @@ -0,0 +1,31 @@ +# Traefik Dynamic Configuration +# Copy to /opt/stacks/traefik/dynamic/routes.yml +# Add custom routes here that aren't defined via Docker labels + +http: + routers: + # Example custom route + # custom-service: + # rule: "Host(`custom.example.com`)" + # entryPoints: + # - websecure + # middlewares: + # - authelia@docker + # tls: + # certResolver: letsencrypt + # service: custom-service + + services: + # Example custom service + # custom-service: + # loadBalancer: + # servers: + # - url: "http://192.168.1.100:8080" + + middlewares: + # Additional middlewares can be defined here + # Example: Rate limiting + # rate-limit: + # rateLimit: + # average: 100 + # burst: 50 diff --git a/config-templates/traefik/traefik.yml b/config-templates/traefik/traefik.yml new file mode 100644 index 0000000..60529fb --- /dev/null +++ b/config-templates/traefik/traefik.yml @@ -0,0 +1,58 @@ +# Traefik Static Configuration +# Copy to /opt/stacks/traefik/traefik.yml + +global: + checkNewVersion: true + sendAnonymousUsage: false + +api: + dashboard: true + insecure: false # Dashboard accessible via Traefik route with Authelia + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + scheme: https + + websecure: + address: ":443" + http: + tls: + certResolver: letsencrypt + +certificatesResolvers: + letsencrypt: + acme: + email: ${ACME_EMAIL} + storage: /acme.json + # Use HTTP challenge (port 80 must be accessible) + httpChallenge: + entryPoint: web + # Or use DNS challenge (requires API token): + # dnsChallenge: + # provider: duckdns + # resolvers: + # - "1.1.1.1:53" + # - "8.8.8.8:53" + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false # Only expose services with traefik.enable=true + network: traefik-network + + file: + directory: /dynamic + watch: true + +log: + level: INFO # DEBUG, INFO, WARN, ERROR + filePath: /var/log/traefik/traefik.log + +accessLog: + filePath: /var/log/traefik/access.log + bufferingSize: 100 diff --git a/docker-compose/README-dockge.md b/docker-compose/README-dockge.md new file mode 100644 index 0000000..ae00ec6 --- /dev/null +++ b/docker-compose/README-dockge.md @@ -0,0 +1,324 @@ +# Docker Compose Stacks - Dockge Structure + +This directory contains Docker Compose files designed for use with Dockge. Each stack should be placed in `/opt/stacks/stack-name/` on your server. + +## Structure + +``` +/opt/stacks/ +├── traefik/ +│ ├── docker-compose.yml # Copy from traefik.yml +│ ├── traefik.yml # Static configuration +│ ├── dynamic/ # Dynamic routes +│ ├── acme.json # SSL certificates (chmod 600) +│ └── .env +├── authelia/ +│ ├── docker-compose.yml # Copy from authelia.yml +│ ├── configuration.yml # Authelia config +│ ├── users_database.yml # User definitions +│ └── .env +├── duckdns/ +│ ├── docker-compose.yml # Copy from duckdns.yml +│ └── .env +├── gluetun/ +│ ├── docker-compose.yml # Copy from gluetun.yml (includes qBittorrent) +│ └── .env +├── infrastructure/ +│ ├── docker-compose.yml # Copy from infrastructure.yml +│ └── .env +├── media/ +│ ├── docker-compose.yml # Copy from media.yml +│ └── .env +├── monitoring/ +│ ├── docker-compose.yml # Copy from monitoring.yml +│ └── .env +└── development/ + ├── docker-compose.yml # Copy from development.yml + └── .env +``` + +## Core Infrastructure Stacks + +### 1. Traefik (REQUIRED - Deploy First) +**File**: `traefik.yml` +**Location**: `/opt/stacks/traefik/` + +Reverse proxy with automatic SSL certificates via Let's Encrypt. + +**Features**: +- Automatic HTTPS with Let's Encrypt +- Docker service discovery via labels +- File-based configuration for AI management +- HTTP to HTTPS redirect + +**Setup**: +```bash +mkdir -p /opt/stacks/traefik/dynamic +cd /opt/stacks/traefik +# Copy traefik.yml to docker-compose.yml +# Copy config templates from config-templates/traefik/ +# Create acme.json and set permissions +touch acme.json && chmod 600 acme.json +# Edit .env with your domain and email +docker compose up -d +``` + +### 2. Authelia (REQUIRED - Deploy Second) +**File**: `authelia.yml` +**Location**: `/opt/stacks/authelia/` + +Single Sign-On (SSO) authentication for all services. + +**Features**: +- Protects services with authentication +- Bypass rules for apps (Jellyfin, Plex) +- Integrates with Traefik via middleware +- TOTP 2FA support + +**Setup**: +```bash +mkdir -p /opt/stacks/authelia +cd /opt/stacks/authelia +# Copy authelia.yml to docker-compose.yml +# Copy config templates from config-templates/authelia/ +# Generate secrets: openssl rand -hex 64 +# Edit configuration.yml and users_database.yml +# Hash password: docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' +docker compose up -d +``` + +### 3. DuckDNS (RECOMMENDED) +**File**: `duckdns.yml` +**Location**: `/opt/stacks/duckdns/` + +Dynamic DNS updater for your domain. + +**Setup**: +```bash +mkdir -p /opt/stacks/duckdns +cd /opt/stacks/duckdns +# Copy duckdns.yml to docker-compose.yml +# Add DUCKDNS_TOKEN and DUCKDNS_SUBDOMAINS to .env +docker compose up -d +``` + +### 4. Gluetun VPN (REQUIRED for torrenting) +**File**: `gluetun.yml` +**Location**: `/opt/stacks/gluetun/` + +VPN client (Surfshark) for routing download clients securely. + +**Includes**: qBittorrent configured to route through VPN + +**Setup**: +```bash +mkdir -p /opt/stacks/gluetun +mkdir -p /opt/stacks/qbittorrent +cd /opt/stacks/gluetun +# Copy gluetun.yml to docker-compose.yml +# Add Surfshark WireGuard credentials to .env +# Get WireGuard config from Surfshark dashboard +docker compose up -d +``` + +## Application Stacks + +### Infrastructure +**File**: `infrastructure.yml` +**Location**: `/opt/stacks/infrastructure/` + +- Pi-hole: Network-wide ad blocking +- Portainer: Docker management UI +- Watchtower: Automatic container updates + +### Media +**File**: `media.yml` +**Location**: `/opt/stacks/media/` + +- Plex: Media streaming (NO Authelia - app access) +- Jellyfin: Open-source streaming (NO Authelia - app access) +- Sonarr: TV show automation (WITH Authelia) +- Radarr: Movie automation (WITH Authelia) +- Prowlarr: Indexer manager (WITH Authelia) + +**Note**: qBittorrent is in gluetun.yml (VPN routing) + +### Monitoring +**File**: `monitoring.yml` +**Location**: `/opt/stacks/monitoring/` + +- Prometheus: Metrics collection +- Grafana: Visualization +- Node Exporter: System metrics +- cAdvisor: Container metrics +- Uptime Kuma: Service monitoring +- Loki: Log aggregation +- Promtail: Log shipping + +### Development +**File**: `development.yml` +**Location**: `/opt/stacks/development/` + +- Code Server: VS Code in browser +- GitLab: Git repository manager +- PostgreSQL: Database +- Redis: In-memory store +- pgAdmin: Database UI +- Jupyter Lab: Data science notebooks +- Node-RED: Automation + +## Networks + +Create these networks before deploying stacks: + +```bash +docker network create traefik-network +docker network create homelab-network +``` + +## Environment Variables + +Each stack needs a `.env` file. Use `/home/runner/work/AI-Homelab/AI-Homelab/.env.example` as a template. + +**Required variables**: +- `DOMAIN`: Your DuckDNS domain (e.g., `yourdomain.duckdns.org`) +- `DUCKDNS_TOKEN`: Your DuckDNS token +- `ACME_EMAIL`: Email for Let's Encrypt +- `AUTHELIA_JWT_SECRET`: Generate with `openssl rand -hex 64` +- `AUTHELIA_SESSION_SECRET`: Generate with `openssl rand -hex 64` +- `AUTHELIA_STORAGE_ENCRYPTION_KEY`: Generate with `openssl rand -hex 64` +- `SURFSHARK_PRIVATE_KEY`: From Surfshark WireGuard config +- `SURFSHARK_ADDRESSES`: From Surfshark WireGuard config + +## Deployment Order + +1. **Create networks**: + ```bash + docker network create traefik-network + docker network create homelab-network + ``` + +2. **Deploy Traefik** (reverse proxy): + ```bash + cd /opt/stacks/traefik + docker compose up -d + ``` + +3. **Deploy Authelia** (SSO): + ```bash + cd /opt/stacks/authelia + docker compose up -d + ``` + +4. **Deploy DuckDNS** (optional but recommended): + ```bash + cd /opt/stacks/duckdns + docker compose up -d + ``` + +5. **Deploy Gluetun** (VPN for downloads): + ```bash + cd /opt/stacks/gluetun + docker compose up -d + ``` + +6. **Deploy other stacks** as needed: + ```bash + cd /opt/stacks/infrastructure + docker compose up -d + + cd /opt/stacks/media + docker compose up -d + ``` + +## Accessing Services + +All services are accessible via your domain: + +- **With Authelia (SSO required)**: + - `https://traefik.yourdomain.duckdns.org` - Traefik dashboard + - `https://portainer.yourdomain.duckdns.org` - Portainer + - `https://sonarr.yourdomain.duckdns.org` - Sonarr + - `https://radarr.yourdomain.duckdns.org` - Radarr + - `https://prowlarr.yourdomain.duckdns.org` - Prowlarr + - `https://qbit.yourdomain.duckdns.org` - qBittorrent + - `https://grafana.yourdomain.duckdns.org` - Grafana + - And more... + +- **Without Authelia (direct app access)**: + - `https://plex.yourdomain.duckdns.org` - Plex + - `https://jellyfin.yourdomain.duckdns.org` - Jellyfin + +- **Authentication page**: + - `https://auth.yourdomain.duckdns.org` - Authelia login + +## AI Management + +The AI assistant (GitHub Copilot) can: + +1. **Add new services**: Creates compose files with proper Traefik labels and Authelia middleware +2. **Modify routes**: Updates Docker labels to change proxy routing +3. **Manage SSO**: Adds or removes Authelia middleware as needed +4. **Configure VPN**: Sets up services to route through Gluetun +5. **Update configurations**: Modifies config files in `/opt/stacks/*/config/` + +All configuration is file-based, allowing the AI to manage everything without web UI dependencies. + +## Storage Strategy + +- **Config files**: `/opt/stacks/stack-name/config/` (on system drive) +- **Small data**: Docker named volumes +- **Large data**: + - Media: `/mnt/media` (separate drive) + - Downloads: `/mnt/downloads` (separate drive) + - Backups: `/mnt/backups` (separate drive) + +## Troubleshooting + +### Service won't start +```bash +cd /opt/stacks/stack-name +docker compose logs -f +``` + +### Check Traefik routing +```bash +docker logs traefik +# Or visit: https://traefik.yourdomain.duckdns.org +``` + +### Test VPN connection +```bash +docker exec gluetun sh -c "curl ifconfig.me" +# Should show VPN IP, not your home IP +``` + +### Authelia issues +```bash +cd /opt/stacks/authelia +docker compose logs -f authelia +# Check configuration.yml for syntax errors +``` + +## Backup Important Files + +Regular backups of: +- `/opt/stacks/` (all compose files and configs) +- `/opt/stacks/traefik/acme.json` (SSL certificates) +- `/opt/stacks/authelia/users_database.yml` (user accounts) +- Environment files (`.env` - store securely, not in git!) + +## Security Notes + +1. **Secrets**: Never commit `.env` files or `acme.json` to git +2. **Authelia**: Use strong passwords, hash them properly +3. **VPN**: Always route download clients through Gluetun +4. **Updates**: Watchtower keeps containers updated automatically +5. **Firewall**: Only expose ports 80 and 443 to the internet + +## Getting Help + +- Check the main [README.md](../README.md) +- Review [Docker Guidelines](../docs/docker-guidelines.md) +- Use GitHub Copilot in VS Code for AI assistance +- Check service-specific logs diff --git a/docker-compose/authelia.yml b/docker-compose/authelia.yml new file mode 100644 index 0000000..0dd329c --- /dev/null +++ b/docker-compose/authelia.yml @@ -0,0 +1,39 @@ +# Authelia SSO Stack +# Single Sign-On authentication for all services +# Place in /opt/stacks/authelia/docker-compose.yml + +services: + authelia: + image: authelia/authelia:4.37 + container_name: authelia + restart: unless-stopped + networks: + - traefik-network + volumes: + - /opt/stacks/authelia/configuration.yml:/config/configuration.yml:ro + - /opt/stacks/authelia/users_database.yml:/config/users_database.yml + - authelia-data:/config + environment: + - TZ=${TZ} + - AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET} + - AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET} + - AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY} + - AUTHELIA_NOTIFIER_SMTP_PASSWORD=${SMTP_PASSWORD} # If using email notifications + labels: + - "traefik.enable=true" + - "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)" + - "traefik.http.routers.authelia.entrypoints=websecure" + - "traefik.http.routers.authelia.tls.certresolver=letsencrypt" + - "traefik.http.services.authelia.loadbalancer.server.port=9091" + # Authelia middleware for other services + - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.${DOMAIN}" + - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true" + - "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email" + +volumes: + authelia-data: + driver: local + +networks: + traefik-network: + external: true diff --git a/docker-compose/duckdns.yml b/docker-compose/duckdns.yml new file mode 100644 index 0000000..16dec0a --- /dev/null +++ b/docker-compose/duckdns.yml @@ -0,0 +1,21 @@ +# DuckDNS Dynamic DNS Stack +# Updates your DuckDNS domain with current public IP +# Place in /opt/stacks/duckdns/docker-compose.yml + +services: + duckdns: + image: lscr.io/linuxserver/duckdns:latest + container_name: duckdns + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ} + - SUBDOMAINS=${DUCKDNS_SUBDOMAINS} # Your subdomain(s), comma separated + - TOKEN=${DUCKDNS_TOKEN} # Your DuckDNS token + - UPDATE_IP=ipv4 # or ipv6, or both + volumes: + - /opt/stacks/duckdns/config:/config + labels: + - "homelab.category=infrastructure" + - "homelab.description=Dynamic DNS updater" diff --git a/docker-compose/gluetun.yml b/docker-compose/gluetun.yml new file mode 100644 index 0000000..534d376 --- /dev/null +++ b/docker-compose/gluetun.yml @@ -0,0 +1,81 @@ +# Gluetun VPN Stack +# VPN client for routing services through Surfshark (or other VPN providers) +# Place in /opt/stacks/gluetun/docker-compose.yml +# Services that need VPN use: network_mode: "service:gluetun" + +services: + gluetun: + image: qmcgaw/gluetun:latest + container_name: gluetun + restart: unless-stopped + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + networks: + - gluetun-network + - traefik-network + ports: + # qBittorrent ports (service runs through Gluetun) + - "8080:8080" # qBittorrent WebUI + - "6881:6881" # qBittorrent TCP + - "6881:6881/udp" # qBittorrent UDP + environment: + - VPN_SERVICE_PROVIDER=surfshark + - VPN_TYPE=wireguard # or openvpn + - WIREGUARD_PRIVATE_KEY=${SURFSHARK_PRIVATE_KEY} + - WIREGUARD_ADDRESSES=${SURFSHARK_ADDRESSES} + - SERVER_COUNTRIES=${VPN_COUNTRY:-Netherlands} # Preferred VPN server country + - TZ=${TZ} + # For OpenVPN instead of WireGuard: + # - OPENVPN_USER=${SURFSHARK_USERNAME} + # - OPENVPN_PASSWORD=${SURFSHARK_PASSWORD} + volumes: + - /opt/stacks/gluetun/config:/gluetun + labels: + - "homelab.category=infrastructure" + - "homelab.description=VPN client for secure routing (Surfshark)" + + # qBittorrent - Torrent client routing through VPN + # Access at: https://qbit.yourdomain.duckdns.org + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:4.6.2 + container_name: qbittorrent + network_mode: "service:gluetun" # Routes all traffic through VPN + depends_on: + - gluetun + volumes: + - /opt/stacks/qbittorrent/config:/config + - /mnt/downloads:/downloads # Large downloads on separate drive + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ} + - WEBUI_PORT=8080 + labels: + - "homelab.category=media" + - "homelab.description=Torrent download client (via VPN)" + # Traefik labels (applied to Gluetun since qBittorrent uses its network) + # Configure these on the Gluetun container instead: + + # Traefik routing for qBittorrent (via Gluetun) + # Since qBittorrent uses Gluetun's network, we add a sidecar label container + qbit-labels: + image: alpine:latest + container_name: qbit-labels + command: tail -f /dev/null + networks: + - traefik-network + labels: + - "traefik.enable=true" + - "traefik.http.routers.qbittorrent.rule=Host(`qbit.${DOMAIN}`)" + - "traefik.http.routers.qbittorrent.entrypoints=websecure" + - "traefik.http.routers.qbittorrent.tls.certresolver=letsencrypt" + - "traefik.http.routers.qbittorrent.middlewares=authelia@docker" + - "traefik.http.services.qbittorrent.loadbalancer.server.url=http://gluetun:8080" + +networks: + gluetun-network: + driver: bridge + traefik-network: + external: true diff --git a/docker-compose/infrastructure.yml b/docker-compose/infrastructure.yml index 6c7c617..2e628d5 100644 --- a/docker-compose/infrastructure.yml +++ b/docker-compose/infrastructure.yml @@ -1,43 +1,44 @@ # Infrastructure Services # Core services that other services depend on +# NOTE: Traefik, Authelia, DuckDNS, and Gluetun have their own compose files +# See traefik.yml, authelia.yml, duckdns.yml, and gluetun.yml services: - # Nginx Proxy Manager - Web-based reverse proxy management - # Access at: http://server-ip:81 - # Default credentials: admin@example.com / changeme - nginx-proxy-manager: - image: jc21/nginx-proxy-manager:2.10.4 - container_name: nginx-proxy-manager - restart: unless-stopped - networks: - - homelab-network - ports: - - "80:80" # HTTP - - "443:443" # HTTPS - - "81:81" # Admin UI - volumes: - - ./config/nginx-proxy-manager/data:/data - - ./config/nginx-proxy-manager/letsencrypt:/etc/letsencrypt - environment: - - PUID=${PUID:-1000} - - PGID=${PGID:-1000} - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:81"] - interval: 30s - timeout: 10s - retries: 3 - labels: - - "homelab.category=infrastructure" - - "homelab.description=Reverse proxy with Let's Encrypt support" - # Pi-hole - Network-wide ad blocker and DNS server - # Access at: http://server-ip:8080/admin + # Access at: http://server-ip:8080/admin or https://pihole.yourdomain.duckdns.org pihole: image: pihole/pihole:2024.01.0 container_name: pihole restart: unless-stopped networks: - homelab-network + - traefik-network + ports: + - "53:53/tcp" # DNS TCP + - "53:53/udp" # DNS UDP + - "8082:80/tcp" # Web interface (changed from 8080 to avoid conflicts) + volumes: + - /opt/stacks/pihole/etc-pihole:/etc/pihole + - /opt/stacks/pihole/etc-dnsmasq.d:/etc/dnsmasq.d + environment: + - TZ=${TZ:-America/New_York} + - WEBPASSWORD=${PIHOLE_PASSWORD:-changeme} + - FTLCONF_LOCAL_IPV4=${SERVER_IP} + dns: + - 127.0.0.1 + - 1.1.1.1 + cap_add: + - NET_ADMIN + labels: + - "homelab.category=infrastructure" + - "homelab.description=Network-wide ad blocking and DNS" + # Traefik labels + - "traefik.enable=true" + - "traefik.http.routers.pihole.rule=Host(`pihole.${DOMAIN}`)" + - "traefik.http.routers.pihole.entrypoints=websecure" + - "traefik.http.routers.pihole.tls.certresolver=letsencrypt" + - "traefik.http.routers.pihole.middlewares=authelia@docker" + - "traefik.http.services.pihole.loadbalancer.server.port=80" ports: - "53:53/tcp" # DNS TCP - "53:53/udp" # DNS UDP @@ -59,16 +60,14 @@ services: - "homelab.description=Network-wide ad blocking and DNS" # Portainer - Docker management UI - # Access at: http://server-ip:9000 + # Access at: https://portainer.yourdomain.duckdns.org portainer: image: portainer/portainer-ce:2.19.4 container_name: portainer restart: unless-stopped networks: - homelab-network - ports: - - "9000:9000" - - "9443:9443" + - traefik-network volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer-data:/data @@ -77,6 +76,13 @@ services: labels: - "homelab.category=infrastructure" - "homelab.description=Docker container management UI" + # Traefik labels + - "traefik.enable=true" + - "traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN}`)" + - "traefik.http.routers.portainer.entrypoints=websecure" + - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" + - "traefik.http.routers.portainer.middlewares=authelia@docker" + - "traefik.http.services.portainer.loadbalancer.server.port=9000" # Watchtower - Automatic container updates # Runs silently in background, no UI @@ -105,3 +111,5 @@ volumes: networks: homelab-network: external: true + traefik-network: + external: true diff --git a/docker-compose/media.yml b/docker-compose/media.yml index fe16afc..ba3f1c2 100644 --- a/docker-compose/media.yml +++ b/docker-compose/media.yml @@ -1,9 +1,12 @@ # Media Services # Services for media management and streaming +# Place in /opt/stacks/media/docker-compose.yml +# NOTE: qBittorrent is configured to use Gluetun VPN (see gluetun.yml) services: # Plex Media Server - Media streaming platform - # Access at: http://server-ip:32400/web + # Access at: https://plex.yourdomain.duckdns.org + # NOTE: No Authelia - allows app access from Roku, Fire TV, mobile, etc. plex: image: plexinc/pms-docker:1.40.0.7998-f68041501 container_name: plex @@ -11,18 +14,16 @@ services: networks: - media-network - homelab-network - ports: - - "32400:32400" # Plex web UI + - traefik-network volumes: - - ./config/plex:/config - - ${MEDIADIR:-/media}:/media:ro + - /opt/stacks/plex/config:/config + - /mnt/media:/media:ro # Large media files on separate drive - plex-transcode:/transcode environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/New_York} - PLEX_CLAIM=${PLEX_CLAIM} - - ADVERTISE_IP=http://${SERVER_IP}:32400/ # Hardware transcoding support # Uncomment ONE of the following options: @@ -44,9 +45,16 @@ services: labels: - "homelab.category=media" - "homelab.description=Plex media streaming server" + # Traefik labels - NO Authelia for app access + - "traefik.enable=true" + - "traefik.http.routers.plex.rule=Host(`plex.${DOMAIN}`)" + - "traefik.http.routers.plex.entrypoints=websecure" + - "traefik.http.routers.plex.tls.certresolver=letsencrypt" + - "traefik.http.services.plex.loadbalancer.server.port=32400" # Jellyfin - Free alternative to Plex - # Access at: http://server-ip:8096 + # Access at: https://jellyfin.yourdomain.duckdns.org + # NOTE: No Authelia - allows app access from Roku, Fire TV, mobile, etc. jellyfin: image: jellyfin/jellyfin:10.8.13 container_name: jellyfin @@ -54,12 +62,11 @@ services: networks: - media-network - homelab-network - ports: - - "8096:8096" + - traefik-network volumes: - - ./config/jellyfin:/config - - ./config/jellyfin/cache:/cache - - ${MEDIADIR:-/media}:/media:ro + - /opt/stacks/jellyfin/config:/config + - /opt/stacks/jellyfin/cache:/cache + - /mnt/media:/media:ro # Large media files on separate drive environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} @@ -70,9 +77,15 @@ services: labels: - "homelab.category=media" - "homelab.description=Open-source media streaming server" + # Traefik labels - NO Authelia for app access + - "traefik.enable=true" + - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.${DOMAIN}`)" + - "traefik.http.routers.jellyfin.entrypoints=websecure" + - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" + - "traefik.http.services.jellyfin.loadbalancer.server.port=8096" # Sonarr - TV show automation - # Access at: http://server-ip:8989 + # Access at: https://sonarr.yourdomain.duckdns.org sonarr: image: lscr.io/linuxserver/sonarr:4.0.0 container_name: sonarr @@ -80,12 +93,11 @@ services: networks: - media-network - homelab-network - ports: - - "8989:8989" + - traefik-network volumes: - - ./config/sonarr:/config - - ${MEDIADIR:-/media}:/media - - ${DOWNLOADDIR:-/downloads}:/downloads + - /opt/stacks/sonarr/config:/config + - /mnt/media:/media + - /mnt/downloads:/downloads # Large downloads on separate drive environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} @@ -93,9 +105,16 @@ services: labels: - "homelab.category=media" - "homelab.description=TV show management and automation" + # Traefik labels with Authelia + - "traefik.enable=true" + - "traefik.http.routers.sonarr.rule=Host(`sonarr.${DOMAIN}`)" + - "traefik.http.routers.sonarr.entrypoints=websecure" + - "traefik.http.routers.sonarr.tls.certresolver=letsencrypt" + - "traefik.http.routers.sonarr.middlewares=authelia@docker" + - "traefik.http.services.sonarr.loadbalancer.server.port=8989" # Radarr - Movie automation - # Access at: http://server-ip:7878 + # Access at: https://radarr.yourdomain.duckdns.org radarr: image: lscr.io/linuxserver/radarr:5.2.6 container_name: radarr @@ -103,12 +122,11 @@ services: networks: - media-network - homelab-network - ports: - - "7878:7878" + - traefik-network volumes: - - ./config/radarr:/config - - ${MEDIADIR:-/media}:/media - - ${DOWNLOADDIR:-/downloads}:/downloads + - /opt/stacks/radarr/config:/config + - /mnt/media:/media + - /mnt/downloads:/downloads # Large downloads on separate drive environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} @@ -116,9 +134,16 @@ services: labels: - "homelab.category=media" - "homelab.description=Movie management and automation" + # Traefik labels with Authelia + - "traefik.enable=true" + - "traefik.http.routers.radarr.rule=Host(`radarr.${DOMAIN}`)" + - "traefik.http.routers.radarr.entrypoints=websecure" + - "traefik.http.routers.radarr.tls.certresolver=letsencrypt" + - "traefik.http.routers.radarr.middlewares=authelia@docker" + - "traefik.http.services.radarr.loadbalancer.server.port=7878" # Prowlarr - Indexer manager - # Access at: http://server-ip:9696 + # Access at: https://prowlarr.yourdomain.duckdns.org prowlarr: image: lscr.io/linuxserver/prowlarr:1.11.4 container_name: prowlarr @@ -126,10 +151,9 @@ services: networks: - media-network - homelab-network - ports: - - "9696:9696" + - traefik-network volumes: - - ./config/prowlarr:/config + - /opt/stacks/prowlarr/config:/config environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} @@ -137,32 +161,19 @@ services: labels: - "homelab.category=media" - "homelab.description=Indexer manager for Sonarr/Radarr" + # Traefik labels with Authelia + - "traefik.enable=true" + - "traefik.http.routers.prowlarr.rule=Host(`prowlarr.${DOMAIN}`)" + - "traefik.http.routers.prowlarr.entrypoints=websecure" + - "traefik.http.routers.prowlarr.tls.certresolver=letsencrypt" + - "traefik.http.routers.prowlarr.middlewares=authelia@docker" + - "traefik.http.services.prowlarr.loadbalancer.server.port=9696" # qBittorrent - Torrent client - # Access at: http://server-ip:8081 - # Default credentials: admin / adminadmin - qbittorrent: - image: lscr.io/linuxserver/qbittorrent:4.6.2 - container_name: qbittorrent - restart: unless-stopped - networks: - - media-network - - homelab-network - ports: - - "8081:8081" # Web UI - - "6881:6881" # Torrent port TCP - - "6881:6881/udp" # Torrent port UDP - volumes: - - ./config/qbittorrent:/config - - ${DOWNLOADDIR:-/downloads}:/downloads - environment: - - PUID=${PUID:-1000} - - PGID=${PGID:-1000} - - TZ=${TZ:-America/New_York} - - WEBUI_PORT=8081 - labels: - - "homelab.category=media" - - "homelab.description=Torrent download client" + # Access at: https://qbit.yourdomain.duckdns.org + # Routes through Gluetun VPN - configure in gluetun.yml + # NOTE: This is a placeholder. Configure qBittorrent in gluetun.yml with network_mode: "service:gluetun" + # See gluetun.yml for the actual qBittorrent configuration volumes: plex-transcode: @@ -173,3 +184,5 @@ networks: driver: bridge homelab-network: external: true + traefik-network: + external: true diff --git a/docker-compose/traefik.yml b/docker-compose/traefik.yml new file mode 100644 index 0000000..adde6b1 --- /dev/null +++ b/docker-compose/traefik.yml @@ -0,0 +1,43 @@ +# Traefik Reverse Proxy Stack +# Main reverse proxy with Let's Encrypt SSL automation +# Place in /opt/stacks/traefik/docker-compose.yml + +services: + traefik: + image: traefik:v2.11 + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - traefik-network + ports: + - "80:80" # HTTP + - "443:443" # HTTPS + - "8080:8080" # Dashboard (protect with Authelia) + volumes: + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - /opt/stacks/traefik/traefik.yml:/traefik.yml:ro + - /opt/stacks/traefik/dynamic:/dynamic:ro + - /opt/stacks/traefik/acme.json:/acme.json + environment: + - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} # If using Cloudflare DNS challenge + - DUCKDNS_TOKEN=${DUCKDNS_TOKEN} # If using DuckDNS + labels: + - "traefik.enable=true" + # Dashboard + - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)" + - "traefik.http.routers.traefik.entrypoints=websecure" + - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" + - "traefik.http.routers.traefik.middlewares=authelia@docker" + - "traefik.http.routers.traefik.service=api@internal" + # Global HTTP to HTTPS redirect + - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" + - "traefik.http.routers.http-catchall.entrypoints=web" + - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + +networks: + traefik-network: + external: true