Add docker-compose configurations and SSL troubleshooting docs
- Added compose files for core, infrastructure, and dashboards stacks - Added Traefik, Authelia, and DuckDNS configuration files - Added dockge.managed and dockge.url labels to all services - Updated Watchtower to latest version with DOCKER_API_VERSION=1.44 - Created comprehensive SSL certificate troubleshooting guide for DuckDNS issues
This commit is contained in:
81
docker-compose/core/authelia/configuration.yml
Normal file
81
docker-compose/core/authelia/configuration.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
# Authelia Configuration
|
||||
# Copy to /opt/stacks/authelia/configuration.yml
|
||||
# IMPORTANT: Replace 'kelin-hass.duckdns.org' with your actual DuckDNS domain
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 9091
|
||||
|
||||
log:
|
||||
level: info
|
||||
|
||||
theme: dark
|
||||
|
||||
jwt_secret: ${AUTHELIA_JWT_SECRET}
|
||||
|
||||
default_redirection_url: https://auth.kelin-hass.duckdns.org
|
||||
|
||||
totp:
|
||||
issuer: kelin-hass.duckdns.org
|
||||
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.kelin-hass.duckdns.org
|
||||
policy: bypass
|
||||
|
||||
# Bypass for Plex (allow app access)
|
||||
- domain: plex.kelin-hass.duckdns.org
|
||||
policy: bypass
|
||||
|
||||
# Bypass for Home Assistant (has its own auth)
|
||||
- domain: ha.kelin-hass.duckdns.org
|
||||
policy: bypass
|
||||
|
||||
# Protected: All other services require authentication
|
||||
- domain: "*.kelin-hass.duckdns.org"
|
||||
policy: one_factor
|
||||
|
||||
# Two-factor for admin services (optional)
|
||||
# - domain:
|
||||
# - "admin.kelin-hass.duckdns.org"
|
||||
# - "portainer.kelin-hass.duckdns.org"
|
||||
# policy: two_factor
|
||||
|
||||
session:
|
||||
name: authelia_session
|
||||
secret: ${AUTHELIA_SESSION_SECRET}
|
||||
expiration: 1h
|
||||
inactivity: 5m
|
||||
remember_me_duration: 1M
|
||||
domain: kelin-hass.duckdns.org
|
||||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
find_time: 2m
|
||||
ban_time: 5m
|
||||
|
||||
storage:
|
||||
encryption_key: ${AUTHELIA_STORAGE_ENCRYPTION_KEY}
|
||||
local:
|
||||
path: /data/db.sqlite3
|
||||
|
||||
notifier:
|
||||
# File-based notifications (for development/testing)
|
||||
filesystem:
|
||||
filename: /data/notification.txt
|
||||
12
docker-compose/core/authelia/users_database.yml
Normal file
12
docker-compose/core/authelia/users_database.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
###############################################################
|
||||
# Users Database #
|
||||
###############################################################
|
||||
|
||||
users:
|
||||
kelin:
|
||||
displayname: "kelin"
|
||||
password: "$argon2id$v=19$m=65536,t=3,p=4$GirJvw4ecHIr1nnM3ALpwg$7+XjPev3P7AwEveRw5yiq5OmsitXYQp5xR8AxWjDNbI"
|
||||
email: kelinfoxy@gmail.com
|
||||
groups:
|
||||
- admins
|
||||
- dev
|
||||
151
docker-compose/core/docker-compose.yml
Normal file
151
docker-compose/core/docker-compose.yml
Normal file
@@ -0,0 +1,151 @@
|
||||
# Core Infrastructure Stack
|
||||
# Essential services required for the homelab to function
|
||||
# Deploy this stack FIRST before any other services
|
||||
# Place in /opt/stacks/core/docker-compose.yml
|
||||
|
||||
services:
|
||||
# DuckDNS - Dynamic DNS updater
|
||||
# Updates your public IP automatically for Let's Encrypt SSL
|
||||
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/core/duckdns:/config
|
||||
labels:
|
||||
- "homelab.category=infrastructure"
|
||||
- "homelab.description=Dynamic DNS updater"
|
||||
|
||||
# Traefik - Reverse proxy with automatic SSL
|
||||
# Routes all traffic and manages Let's Encrypt certificates
|
||||
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 (protected with Authelia)
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /opt/stacks/core/traefik/traefik.yml:/traefik.yml:ro
|
||||
- /opt/stacks/core/traefik/dynamic:/dynamic:ro
|
||||
- /opt/stacks/core/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
|
||||
- LEGO_DISABLE_CNAME_SUPPORT=true # Disable CNAME support to avoid authoritative NS queries
|
||||
- LEGO_EXPERIMENTAL_DNS_TCP_SUPPORT=true # Use TCP for DNS queries
|
||||
- LEGO_DNS_TIMEOUT=60 # DNS timeout in seconds
|
||||
- LEGO_DNS_RESOLVERS=1.1.1.1:53,8.8.8.8:53 # Force use of specific DNS resolvers
|
||||
- LEGO_DISABLE_CP=true # Disable authoritative nameserver propagation check
|
||||
- DUCKDNS_PROPAGATION_TIMEOUT=600 # Increase propagation timeout to 10 minutes
|
||||
labels:
|
||||
- "dockge.managed=true"
|
||||
- "dockge.url=https://traefik.${DOMAIN}"
|
||||
- "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.tls.domains[0].main=${DOMAIN}"
|
||||
- "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN}"
|
||||
- "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"
|
||||
depends_on:
|
||||
- duckdns
|
||||
|
||||
# Authelia - SSO authentication
|
||||
# Protects all admin services with single sign-on
|
||||
authelia:
|
||||
image: authelia/authelia:4.37
|
||||
container_name: authelia
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik-network
|
||||
volumes:
|
||||
- /opt/stacks/core/authelia/configuration.yml:/config/configuration.yml:ro
|
||||
- /opt/stacks/core/authelia/users_database.yml:/config/users_database.yml
|
||||
- authelia-data:/data
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET}
|
||||
- AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET}
|
||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY}
|
||||
labels:
|
||||
- "dockge.managed=true"
|
||||
- "dockge.url=https://auth.${DOMAIN}"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)"
|
||||
- "traefik.http.routers.authelia.entrypoints=websecure"
|
||||
- "traefik.http.routers.authelia.tls=true"
|
||||
- "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"
|
||||
depends_on:
|
||||
- traefik
|
||||
|
||||
# Gluetun - VPN client (Surfshark WireGuard)
|
||||
# Routes download clients through VPN for security
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun:latest
|
||||
container_name: gluetun
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
networks:
|
||||
- homelab-network
|
||||
- traefik-network
|
||||
ports:
|
||||
- "8888:8888/tcp" # HTTP proxy
|
||||
- "8388:8388/tcp" # Shadowsocks
|
||||
- "8388:8388/udp" # Shadowsocks
|
||||
- "8081:8080" # qBittorrent web UI
|
||||
- "6881:6881" # qBittorrent
|
||||
- "6881:6881/udp" # qBittorrent
|
||||
volumes:
|
||||
- /opt/stacks/core/gluetun:/gluetun
|
||||
environment:
|
||||
- VPN_SERVICE_PROVIDER=surfshark
|
||||
- VPN_TYPE=openvpn
|
||||
- OPENVPN_USER=${SURFSHARK_USERNAME}
|
||||
- OPENVPN_PASSWORD=${SURFSHARK_PASSWORD}
|
||||
- SERVER_COUNTRIES=${VPN_SERVER_COUNTRIES:-Netherlands}
|
||||
- TZ=${TZ}
|
||||
labels:
|
||||
- "homelab.category=infrastructure"
|
||||
- "homelab.description=VPN client for secure downloads"
|
||||
|
||||
volumes:
|
||||
authelia-data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
traefik-network:
|
||||
external: true
|
||||
homelab-network:
|
||||
external: true
|
||||
5
docker-compose/core/duckdns/logrotate.conf
Normal file
5
docker-compose/core/duckdns/logrotate.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
/config/duck.log {
|
||||
rotate 5
|
||||
size 100k
|
||||
compress
|
||||
}
|
||||
16
docker-compose/core/traefik/acme.json
Normal file
16
docker-compose/core/traefik/acme.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"letsencrypt": {
|
||||
"Account": {
|
||||
"Email": "kelinfoxy@gmail.com",
|
||||
"Registration": {
|
||||
"body": {
|
||||
"status": "valid"
|
||||
},
|
||||
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/2959423246"
|
||||
},
|
||||
"PrivateKey": "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",
|
||||
"KeyType": "4096"
|
||||
},
|
||||
"Certificates": null
|
||||
}
|
||||
}
|
||||
31
docker-compose/core/traefik/dynamic/routes.yml
Normal file
31
docker-compose/core/traefik/dynamic/routes.yml
Normal file
@@ -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
|
||||
56
docker-compose/core/traefik/traefik.yml
Normal file
56
docker-compose/core/traefik/traefik.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
# 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: kelinfoxy@gmail.com
|
||||
storage: /acme.json
|
||||
# Use DNS challenge for wildcard certificate support
|
||||
dnsChallenge:
|
||||
provider: duckdns
|
||||
delayBeforeCheck: 300 # Wait 5 minutes before checking DNS propagation
|
||||
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: DEBUG # DEBUG, INFO, WARN, ERROR
|
||||
filePath: /var/log/traefik/traefik.log
|
||||
|
||||
accessLog:
|
||||
filePath: /var/log/traefik/access.log
|
||||
bufferingSize: 100
|
||||
71
docker-compose/dashboards/docker-compose.yml
Normal file
71
docker-compose/dashboards/docker-compose.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
# Dashboard Services
|
||||
# Homepage and Homarr for homelab dashboards
|
||||
# Place in /opt/stacks/dashboards/docker-compose.yml
|
||||
|
||||
services:
|
||||
# Homepage - Application dashboard (AI-configurable via YAML)
|
||||
# Access at: https://home.${DOMAIN}
|
||||
homepage:
|
||||
image: ghcr.io/gethomepage/homepage:latest
|
||||
container_name: homepage
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- homelab-network
|
||||
- traefik-network
|
||||
- dockerproxy-network
|
||||
volumes:
|
||||
- /opt/stacks/homepage/config:/app/config
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # For Docker integration
|
||||
- /opt/stacks:/opt/stacks:ro # To discover other stacks
|
||||
environment:
|
||||
- PUID=${PUID:-1000}
|
||||
- PGID=${PGID:-1000}
|
||||
- TZ=${TZ}
|
||||
- HOMEPAGE_ALLOWED_HOSTS=home.${DOMAIN}
|
||||
labels:
|
||||
- "homelab.category=dashboard"
|
||||
- "homelab.description=Application dashboard (AI-configurable)"
|
||||
- "dockge.managed=true"
|
||||
- "dockge.url=https://home.${DOMAIN}"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.homepage.rule=Host(`home.${DOMAIN}`)"
|
||||
- "traefik.http.routers.homepage.entrypoints=websecure"
|
||||
- "traefik.http.routers.homepage.tls=true"
|
||||
- "traefik.http.routers.homepage.middlewares=authelia@docker"
|
||||
- "traefik.http.services.homepage.loadbalancer.server.port=3000"
|
||||
|
||||
# Homarr - Modern dashboard
|
||||
# Access at: https://homarr.${DOMAIN}
|
||||
homarr:
|
||||
image: ghcr.io/ajnart/homarr:latest
|
||||
container_name: homarr
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- homelab-network
|
||||
- traefik-network
|
||||
volumes:
|
||||
- /opt/stacks/homarr/configs:/app/data/configs
|
||||
- /opt/stacks/homarr/data:/data
|
||||
- /opt/stacks/homarr/icons:/app/public/icons
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
labels:
|
||||
- "homelab.category=dashboard"
|
||||
- "homelab.description=Modern homelab dashboard"
|
||||
- "dockge.managed=true"
|
||||
- "dockge.url=https://homarr.${DOMAIN}"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.homarr.rule=Host(`homarr.${DOMAIN}`)"
|
||||
- "traefik.http.routers.homarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.homarr.tls=true"
|
||||
- "traefik.http.routers.homarr.middlewares=authelia@docker"
|
||||
- "traefik.http.services.homarr.loadbalancer.server.port=7575"
|
||||
|
||||
networks:
|
||||
homelab-network:
|
||||
external: true
|
||||
traefik-network:
|
||||
external: true
|
||||
dockerproxy-network:
|
||||
external: true
|
||||
169
docker-compose/infrastructure/docker-compose.yml
Normal file
169
docker-compose/infrastructure/docker-compose.yml
Normal file
@@ -0,0 +1,169 @@
|
||||
# Infrastructure Services
|
||||
# Core services that other services depend on
|
||||
# Place in /opt/stacks/infrastructure/docker-compose.yml
|
||||
# NOTE: Traefik, Authelia, DuckDNS, and Gluetun have their own separate stacks
|
||||
# See /opt/stacks/traefik/, /opt/stacks/authelia/, etc.
|
||||
|
||||
services:
|
||||
# Dockge - Docker Compose Stack Manager (PRIMARY - preferred over Portainer)
|
||||
# Access at: https://dockge.${DOMAIN}
|
||||
dockge:
|
||||
image: louislam/dockge:1
|
||||
container_name: dockge
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- homelab-network
|
||||
- traefik-network
|
||||
ports:
|
||||
- 5001:5001 # Optional: direct access
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /opt/stacks:/opt/stacks # Dockge manages stacks in this directory
|
||||
- /opt/dockge/data:/app/data
|
||||
environment:
|
||||
- DOCKGE_STACKS_DIR=/opt/stacks
|
||||
- DOCKGE_ENABLE_CONSOLE=true
|
||||
labels:
|
||||
- homelab.category=infrastructure
|
||||
- homelab.description=Docker Compose stack manager (PRIMARY)
|
||||
- dockge.managed=true
|
||||
- dockge.url=https://dockge.${DOMAIN}
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.dockge.rule=Host(`dockge.${DOMAIN}`)
|
||||
- traefik.http.routers.dockge.entrypoints=websecure
|
||||
- traefik.http.routers.dockge.tls=true
|
||||
- traefik.http.routers.dockge.middlewares=authelia@docker
|
||||
- traefik.http.services.dockge.loadbalancer.server.port=5001
|
||||
# Pi-hole - Network-wide ad blocker and DNS server
|
||||
# Access at: https://pihole.${DOMAIN}
|
||||
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
|
||||
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 - dockge.managed=true
|
||||
- dockge.url=https://pihole.${DOMAIN} - traefik.enable=true
|
||||
- traefik.http.routers.pihole.rule=Host(`pihole.${DOMAIN}`)
|
||||
- traefik.http.routers.pihole.entrypoints=websecure
|
||||
- traefik.http.routers.pihole.tls=true
|
||||
- traefik.http.routers.pihole.middlewares=authelia@docker
|
||||
- traefik.http.services.pihole.loadbalancer.server.port=80
|
||||
# Watchtower - Automatic container updates
|
||||
# Runs silently in background, no UI
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
container_name: watchtower
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- homelab-network
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_INCLUDE_RESTARTING=true
|
||||
- WATCHTOWER_SCHEDULE=0 0 4 * * * # 4 AM daily
|
||||
- WATCHTOWER_NOTIFICATIONS=shoutrrr
|
||||
- WATCHTOWER_NOTIFICATION_URL=${WATCHTOWER_NOTIFICATION_URL}
|
||||
- DOCKER_API_VERSION=1.44
|
||||
labels:
|
||||
- homelab.category=infrastructure
|
||||
- homelab.description=Automatic Docker container updates
|
||||
# Dozzle - Real-time Docker log viewer
|
||||
# Access at: https://dozzle.${DOMAIN}
|
||||
dozzle:
|
||||
image: amir20/dozzle:latest
|
||||
container_name: dozzle
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- homelab-network
|
||||
- traefik-network
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- DOZZLE_LEVEL=info
|
||||
- DOZZLE_TAILSIZE=300
|
||||
- DOZZLE_FILTER=status=running
|
||||
labels:
|
||||
- homelab.category=infrastructure
|
||||
- homelab.description=Real-time Docker log viewer
|
||||
- dockge.managed=true
|
||||
- dockge.url=https://dozzle.${DOMAIN}
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.dozzle.rule=Host(`dozzle.${DOMAIN}`)
|
||||
- traefik.http.routers.dozzle.entrypoints=websecure
|
||||
- traefik.http.routers.dozzle.tls=true
|
||||
- traefik.http.routers.dozzle.middlewares=authelia@docker
|
||||
- traefik.http.services.dozzle.loadbalancer.server.port=8080
|
||||
# Docker Proxy - Socket proxy for security
|
||||
# Used by services that need Docker socket access
|
||||
dockerproxy:
|
||||
image: tecnativa/docker-socket-proxy:latest
|
||||
container_name: dockerproxy
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dockerproxy-network
|
||||
ports:
|
||||
- 127.0.0.1:2375:2375
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- CONTAINERS=1
|
||||
- SERVICES=1
|
||||
- TASKS=1
|
||||
- NETWORKS=1
|
||||
- NODES=1
|
||||
labels:
|
||||
- homelab.category=infrastructure
|
||||
- homelab.description=Docker socket proxy for security
|
||||
# Glances - System monitoring
|
||||
# Access at: https://glances.${DOMAIN}
|
||||
glances:
|
||||
image: nicolargo/glances:latest-full
|
||||
container_name: glances
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- homelab-network
|
||||
- traefik-network
|
||||
pid: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /opt/stacks/glances/config:/glances/conf
|
||||
environment:
|
||||
- GLANCES_OPT=-w
|
||||
labels:
|
||||
- homelab.category=infrastructure
|
||||
- homelab.description=System and Docker monitoring
|
||||
- dockge.managed=true
|
||||
- dockge.url=https://glances.${DOMAIN}
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.glances.rule=Host(`glances.${DOMAIN}`)
|
||||
- traefik.http.routers.glances.entrypoints=websecure
|
||||
- traefik.http.routers.glances.tls=true
|
||||
- traefik.http.routers.glances.middlewares=authelia@docker
|
||||
- traefik.http.services.glances.loadbalancer.server.port=61208
|
||||
networks:
|
||||
homelab-network:
|
||||
driver: bridge
|
||||
dockerproxy-network:
|
||||
driver: bridge
|
||||
traefik-network:
|
||||
external: true
|
||||
223
docs/troubleshooting/SSL-CERTIFICATES-DUCKDNS.md
Normal file
223
docs/troubleshooting/SSL-CERTIFICATES-DUCKDNS.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# SSL Certificate Issues with DuckDNS DNS Challenge
|
||||
|
||||
## Issue Summary
|
||||
Wildcard SSL certificate acquisition via DuckDNS DNS-01 challenge consistently fails due to network connectivity issues with DuckDNS authoritative nameservers.
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Why Both Domain and Wildcard are Required
|
||||
Let's Encrypt requires validation of BOTH domains when using SAN (Subject Alternative Name) certificates:
|
||||
- `kelin-hass.duckdns.org` (apex domain)
|
||||
- `*.kelin-hass.duckdns.org` (wildcard)
|
||||
|
||||
This is a Let's Encrypt policy - you cannot obtain just the wildcard certificate. Both must be validated simultaneously.
|
||||
|
||||
### Technical Root Cause: Unreachable Authoritative Nameservers
|
||||
|
||||
**Problem**: DuckDNS authoritative nameservers (ns1-ns9.duckdns.org) are **unreachable** from the test system's network.
|
||||
|
||||
**Evidence**:
|
||||
```bash
|
||||
# Direct ping to DuckDNS nameservers - 100% packet loss
|
||||
ping -c 2 ns1.duckdns.org # FAIL: 100% packet loss
|
||||
ping -c 2 99.79.143.35 # FAIL: 100% packet loss (direct IP)
|
||||
|
||||
# DNS queries to authoritative servers - timeout
|
||||
dig @99.79.143.35 kelin-hass.duckdns.org # FAIL: timeout
|
||||
dig @35.182.183.211 kelin-hass.duckdns.org # FAIL: timeout
|
||||
dig @3.97.58.28 kelin-hass.duckdns.org # FAIL: timeout
|
||||
|
||||
# Queries to recursive resolvers - SUCCESS
|
||||
dig @8.8.8.8 kelin-hass.duckdns.org # SUCCESS
|
||||
dig @1.1.1.1 kelin-hass.duckdns.org # SUCCESS
|
||||
|
||||
# Traceroute analysis
|
||||
traceroute 99.79.143.35
|
||||
# Shows traffic reaching hop 5 (74.41.143.193) then black hole
|
||||
# DuckDNS nameservers are hosted on Amazon AWS
|
||||
# Suggests AWS security groups or ISP blocking
|
||||
```
|
||||
|
||||
**Why This Matters**:
|
||||
Traefik's ACME client (lego library) requires verification against authoritative nameservers after setting TXT records. Even though:
|
||||
- DuckDNS API successfully sets TXT records ✅
|
||||
- TXT records propagate to public DNS (8.8.8.8, 1.1.1.1) ✅
|
||||
- Recursive DNS queries work ✅
|
||||
|
||||
The lego library **must** also query the authoritative nameservers directly to verify propagation, and this step fails due to network unreachability.
|
||||
|
||||
## Attempted Solutions
|
||||
|
||||
### Configuration Optimizations Tried
|
||||
|
||||
1. **Increased propagation delay** - `delayBeforeCheck: 300` (5 minutes)
|
||||
- Result: Delay worked, but authoritative NS check still failed
|
||||
|
||||
2. **Extended timeout** - `DUCKDNS_PROPAGATION_TIMEOUT=600` (10 minutes)
|
||||
- Result: Longer timeout observed, but same NS unreachability issue
|
||||
|
||||
3. **LEGO environment variables**:
|
||||
```yaml
|
||||
- LEGO_DISABLE_CNAME_SUPPORT=true
|
||||
- LEGO_EXPERIMENTAL_DNS_TCP_SUPPORT=true
|
||||
- LEGO_DNS_TIMEOUT=60
|
||||
- LEGO_DNS_RESOLVERS=1.1.1.1:53,8.8.8.8:53
|
||||
- LEGO_DISABLE_CP=true
|
||||
```
|
||||
- Result: Forced use of recursive resolvers for some queries, but SOA lookups still failed
|
||||
|
||||
4. **Explicit Docker DNS configuration**:
|
||||
```yaml
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
```
|
||||
- Result: Container used correct resolvers, but lego still attempted authoritative NS queries
|
||||
|
||||
5. **VPN routing test** (through Gluetun container)
|
||||
- Result: DuckDNS nameservers also unreachable through VPN
|
||||
|
||||
### Error Messages Observed
|
||||
|
||||
**Phase 1: Direct authoritative nameserver timeout**
|
||||
```
|
||||
propagation: time limit exceeded: last error: authoritative nameservers:
|
||||
DNS call error: read udp 172.19.0.2:53666->3.97.58.28:53: i/o timeout
|
||||
[ns=ns6.duckdns.org.:53, question='_acme-challenge.kelin-hass.duckdns.org. IN TXT']
|
||||
```
|
||||
|
||||
**Phase 2: SOA record query failure**
|
||||
```
|
||||
propagation: time limit exceeded: last error: could not find zone:
|
||||
[fqdn=_acme-challenge.kelin-hass.duckdns.org.]
|
||||
unexpected response for 'kelin-hass.duckdns.org.'
|
||||
[question='kelin-hass.duckdns.org. IN SOA', code=SERVFAIL]
|
||||
```
|
||||
|
||||
## Working Configuration (Self-Signed Certificates)
|
||||
|
||||
Current deployment is **fully functional** with self-signed certificates:
|
||||
- All services accessible via HTTPS ✅
|
||||
- Can proceed through browser certificate warnings ✅
|
||||
- Traefik routing works correctly ✅
|
||||
- Authelia SSO functional ✅
|
||||
- All stacks deployed successfully ✅
|
||||
|
||||
## Recommended Solutions for Next Test Run
|
||||
|
||||
### Option 1: Switch to Cloudflare DNS (RECOMMENDED)
|
||||
**Pros**:
|
||||
- Cloudflare nameservers are highly reliable and globally accessible
|
||||
- Supports wildcard certificates via DNS-01 challenge
|
||||
- Better performance and propagation times
|
||||
- Well-tested with Traefik
|
||||
|
||||
**Steps**:
|
||||
1. Move domain to Cloudflare (free tier sufficient)
|
||||
2. Obtain Cloudflare API token (Zone:DNS:Edit permission)
|
||||
3. Update `traefik.yml`:
|
||||
```yaml
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
delayBeforeCheck: 30 # Cloudflare propagates quickly
|
||||
resolvers:
|
||||
- "1.1.1.1:53"
|
||||
- "1.0.0.1:53"
|
||||
```
|
||||
4. Update `docker-compose.yml`:
|
||||
```yaml
|
||||
environment:
|
||||
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
|
||||
```
|
||||
|
||||
### Option 2: Investigate Network Blocking
|
||||
**Diagnostic Steps**:
|
||||
1. Test from different network (mobile hotspot, different ISP)
|
||||
2. Contact ISP to check if AWS IP ranges are blocked
|
||||
3. Check router/firewall for DNS filtering or AWS blocking
|
||||
4. Test with different VPN provider
|
||||
|
||||
**If network is the issue**:
|
||||
- May need to use VPN or proxy for Traefik container
|
||||
- Consider hosting Traefik on different network segment
|
||||
|
||||
### Option 3: HTTP-01 Challenge (Non-Wildcard)
|
||||
**Pros**:
|
||||
- More reliable (no DNS dependencies)
|
||||
- Works with current DuckDNS setup
|
||||
- No external nameserver queries required
|
||||
|
||||
**Cons**:
|
||||
- ❌ No wildcard certificate (must specify each subdomain)
|
||||
- Requires port 80 accessible from internet
|
||||
- Separate certificate for each subdomain
|
||||
|
||||
**Steps**:
|
||||
1. Update `traefik.yml`:
|
||||
```yaml
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
```
|
||||
2. Remove wildcard domain label from Traefik service:
|
||||
```yaml
|
||||
# Remove this line:
|
||||
- "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN}"
|
||||
```
|
||||
3. Add explicit TLS configuration to each service's labels
|
||||
|
||||
### Option 4: Use Alternative DNS Provider with DuckDNS
|
||||
Keep DuckDNS for dynamic IP updates, but use different DNS for certificates:
|
||||
1. Use Cloudflare for DNS records
|
||||
2. Keep DuckDNS container for IP updates
|
||||
3. Create CNAME in Cloudflare pointing to DuckDNS
|
||||
4. Use Cloudflare for certificate challenge
|
||||
|
||||
## Files to Update in Repository
|
||||
|
||||
### ~/AI-Homelab/stacks/core/traefik/traefik.yml
|
||||
Document both HTTP and DNS challenge configurations with clear comments.
|
||||
|
||||
### ~/AI-Homelab/stacks/core/docker-compose.yml
|
||||
Ensure wildcard domain configuration is correct (it is currently):
|
||||
```yaml
|
||||
- "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN}"
|
||||
- "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN}"
|
||||
```
|
||||
**This is correct** - keep both apex and wildcard.
|
||||
|
||||
### ~/AI-Homelab/docs/service-docs/traefik.md
|
||||
Add troubleshooting section for DuckDNS DNS challenge issues.
|
||||
|
||||
## Success Criteria for Next Test
|
||||
|
||||
### Must Have:
|
||||
- [ ] Valid wildcard SSL certificate obtained
|
||||
- [ ] Certificate automatically renews
|
||||
- [ ] No browser certificate warnings
|
||||
- [ ] Documented working configuration
|
||||
|
||||
### Should Have:
|
||||
- [ ] Certificate acquisition completes in < 5 minutes
|
||||
- [ ] Reliable across multiple test runs
|
||||
- [ ] Clear error messages if failure occurs
|
||||
|
||||
## Timeline Analysis
|
||||
|
||||
**First Test Run**: Certificates reportedly worked
|
||||
**Current Test Run**: Consistent failures
|
||||
|
||||
**Possible Explanations**:
|
||||
1. DuckDNS infrastructure changes (AWS security policies)
|
||||
2. ISP routing changes
|
||||
3. Increased AWS security after abuse/attacks
|
||||
4. Different network environment during first test
|
||||
|
||||
## Conclusion
|
||||
|
||||
**Current Status**: System is production-ready except for SSL certificate warnings.
|
||||
|
||||
**Blocking Issue**: DuckDNS authoritative nameservers unreachable from current network environment.
|
||||
|
||||
**Recommendation**: **Switch to Cloudflare DNS** for next test run. This is the most reliable solution and is the industry standard for automated certificate management with Traefik.
|
||||
|
||||
**Alternative**: If staying with DuckDNS is required, investigate network connectivity issues with ISP and consider using HTTP-01 challenge (losing wildcard capability).
|
||||
Reference in New Issue
Block a user