added nginx to traefik migration changes and docs
This commit is contained in:
@@ -1,9 +1,48 @@
|
||||
# ELK + Fleet — sized for: Phenom II X4, 8GB DDR3, 120GB SSD
|
||||
#
|
||||
# Scope right now = what the course has actually covered: Elasticsearch,
|
||||
# Logstash, Kibana, Fleet Server. Filebeat/auditbeat/metricbeat/packetbeat/
|
||||
# apm-server are deliberately NOT in here yet — add them as their own
|
||||
# service blocks (same pattern as elasticsearch/kibana below) when you
|
||||
# reach that lesson, so you're not paying RAM for services you can't use
|
||||
# yet.
|
||||
#
|
||||
# Services are grouped with Compose "profiles" so you can turn whole
|
||||
# lesson-chunks on/off without editing this file — just edit
|
||||
# COMPOSE_PROFILES in .env. Core (elasticsearch + kibana) has no profile,
|
||||
# so it's always included.
|
||||
#
|
||||
# Rough memory budget on this box (no swap):
|
||||
# elasticsearch ~1.5 GB
|
||||
# logstash ~0.9 GB
|
||||
# kibana ~0.9 GB
|
||||
# fleet-server ~0.4 GB
|
||||
# ---------------------------------
|
||||
# total ~3.7 GB (you have ~5.9 GB free right now)
|
||||
#
|
||||
# That leaves headroom for Docker/Traefik/host overhead and for the first
|
||||
# enrolled Elastic Agent once you get there (~0.25-0.3 GB more).
|
||||
#
|
||||
# REGISTRY: docker.elastic.co sits behind Amazon CloudFront, which
|
||||
# geo-blocks a handful of sanctioned countries (confirmed pattern for
|
||||
# Russia/Belarus, same CloudFront-based mechanism affects Iran) — that's
|
||||
# what "context deadline exceeded" against docker.elastic.co/v2/ almost
|
||||
# always means, not a real network fault. These images pull instead from
|
||||
# Docker Hub (elasticsearch, kibana, logstash under the official
|
||||
# library/ namespace, elastic-agent under elastic/) — Elastic pushes the
|
||||
# exact same images there and keeps them current (checked: 8.19.19
|
||||
# present on all four as of this writing). Different CDN, so it isn't
|
||||
# guaranteed to be reachable either — test with
|
||||
# `docker pull elasticsearch:${ELK_VERSION}` before assuming it works.
|
||||
|
||||
services:
|
||||
elasticsearch:
|
||||
image: elasticsearch:8.19.19
|
||||
image: elasticsearch:${ELK_VERSION}
|
||||
container_name: elasticsearch
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- node.name=elasticsearch
|
||||
- cluster.name=elk-lab
|
||||
- discovery.type=single-node
|
||||
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
|
||||
- xpack.security.enabled=true
|
||||
@@ -13,19 +52,37 @@ services:
|
||||
- ES_JAVA_OPTS=-Xms1g -Xmx1g
|
||||
mem_limit: 1500m
|
||||
cpus: 1.5
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
volumes:
|
||||
- es-data:/usr/share/elasticsearch/data
|
||||
ports:
|
||||
- "9200:9200"
|
||||
restart: unless-stopped
|
||||
- "9200:9200" # keep for now, drop once you trust Traefik routing
|
||||
networks:
|
||||
- elk
|
||||
- web_net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s -u elastic:${ELASTIC_PASSWORD} http://localhost:9200 | grep -q cluster_name"]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.elasticsearch.rule=Host(`elasticsearch.home.server`)"
|
||||
- "traefik.http.routers.elasticsearch.entrypoints=websecure"
|
||||
- "traefik.http.routers.elasticsearch.tls=true"
|
||||
- "traefik.http.routers.elasticsearch.service=elasticsearch"
|
||||
- "traefik.http.routers.elasticsearch.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.elasticsearch-http.rule=Host(`elasticsearch.home.server`)"
|
||||
- "traefik.http.routers.elasticsearch-http.entrypoints=web"
|
||||
- "traefik.http.routers.elasticsearch-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.elasticsearch.loadbalancer.server.port=9200"
|
||||
|
||||
kibana-setup:
|
||||
image: elasticsearch:8.19.19
|
||||
image: elasticsearch:${ELK_VERSION}
|
||||
container_name: kibana-setup
|
||||
depends_on:
|
||||
elasticsearch:
|
||||
@@ -43,13 +100,16 @@ services:
|
||||
- "Content-Type: application/json"
|
||||
- "-d"
|
||||
- "{\"password\":\"${KIBANA_PASSWORD}\"}"
|
||||
networks:
|
||||
- elk
|
||||
|
||||
kibana:
|
||||
image: kibana:8.19.19
|
||||
image: kibana:${ELK_VERSION}
|
||||
container_name: kibana
|
||||
depends_on:
|
||||
kibana-setup:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
|
||||
- ELASTICSEARCH_USERNAME=kibana_system
|
||||
@@ -57,12 +117,125 @@ services:
|
||||
- XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
|
||||
- XPACK_SECURITY_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
|
||||
- XPACK_REPORTING_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
|
||||
- NODE_OPTIONS=--max-old-space-size=1024
|
||||
mem_limit: 1200m
|
||||
- XPACK_FLEET_ENABLED=true
|
||||
- SERVER_PUBLICBASEURL=https://kibana.home.server
|
||||
- NODE_OPTIONS=--max-old-space-size=896
|
||||
mem_limit: 900m
|
||||
cpus: 1
|
||||
ports:
|
||||
- "5601:5601"
|
||||
- "5601:5601" # keep for now
|
||||
networks:
|
||||
- elk
|
||||
- web_net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s http://localhost:5601/api/status | grep -q '\"level\":\"available\"'"]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.kibana.rule=Host(`kibana.home.server`)"
|
||||
- "traefik.http.routers.kibana.entrypoints=websecure"
|
||||
- "traefik.http.routers.kibana.tls=true"
|
||||
- "traefik.http.routers.kibana.service=kibana"
|
||||
- "traefik.http.routers.kibana.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.kibana-http.rule=Host(`kibana.home.server`)"
|
||||
- "traefik.http.routers.kibana-http.entrypoints=web"
|
||||
- "traefik.http.routers.kibana-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.kibana.loadbalancer.server.port=5601"
|
||||
|
||||
logstash:
|
||||
image: logstash:${ELK_VERSION}
|
||||
container_name: logstash
|
||||
profiles: ["logstash"]
|
||||
depends_on:
|
||||
elasticsearch:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- LS_JAVA_OPTS=-Xms512m -Xmx512m
|
||||
- ELASTICSEARCH_USERNAME=elastic
|
||||
- ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
|
||||
mem_limit: 900m
|
||||
cpus: 1
|
||||
volumes:
|
||||
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
|
||||
- ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
|
||||
ports:
|
||||
- "5044:5044" # beats input
|
||||
- "9600:9600" # monitoring API
|
||||
networks:
|
||||
- elk
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s http://localhost:9600 | grep -q ok"]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
fleet-server:
|
||||
image: elastic/elastic-agent:${ELK_VERSION}
|
||||
container_name: fleet-server
|
||||
profiles: ["fleet"]
|
||||
depends_on:
|
||||
kibana:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# No service token here on purpose — the agent bootstraps Fleet
|
||||
# Server itself (creates the policy, mints its own token) as long
|
||||
# as it can log into Kibana with real credentials. Without these
|
||||
# two it silently falls back to elastic/changeme and fails auth.
|
||||
- ELASTICSEARCH_USERNAME=elastic
|
||||
- ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
|
||||
- FLEET_SERVER_ENABLE=true
|
||||
- FLEET_SERVER_ELASTICSEARCH_HOST=http://elasticsearch:9200
|
||||
- FLEET_SERVER_INSECURE_HTTP=true
|
||||
- FLEET_SERVER_HOST=0.0.0.0
|
||||
- FLEET_SERVER_PORT=8220
|
||||
- KIBANA_HOST=http://kibana:5601
|
||||
- KIBANA_FLEET_HOST=http://kibana:5601
|
||||
mem_limit: 400m
|
||||
cpus: 0.5
|
||||
volumes:
|
||||
- fleet-server-state:/usr/share/elastic-agent/state
|
||||
ports:
|
||||
- "8220:8220"
|
||||
networks:
|
||||
- elk
|
||||
- web_net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8220/api/status | grep -q HEALTHY"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.fleet.rule=Host(`fleet.home.server`)"
|
||||
- "traefik.http.routers.fleet.entrypoints=websecure"
|
||||
- "traefik.http.routers.fleet.tls=true"
|
||||
- "traefik.http.routers.fleet.service=fleet"
|
||||
- "traefik.http.routers.fleet.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.fleet-http.rule=Host(`fleet.home.server`)"
|
||||
- "traefik.http.routers.fleet-http.entrypoints=web"
|
||||
- "traefik.http.routers.fleet-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.fleet.loadbalancer.server.port=8220"
|
||||
|
||||
# --- Next lesson, not started yet ---
|
||||
# Once you reach filebeat.yml / auditbeat.yml etc, add each beat here as
|
||||
# its own service (profiles: ["beats"]), same pattern as logstash above:
|
||||
# mount its .yml under ./<beat-name>/, point ELASTICSEARCH_HOSTNAME etc
|
||||
# at elasticsearch/kibana over the `elk` network, and give it a named
|
||||
# volume for its data dir. No Traefik labels needed for beats — they're
|
||||
# agents, not web UIs.
|
||||
|
||||
volumes:
|
||||
es-data:
|
||||
fleet-server-state:
|
||||
|
||||
networks:
|
||||
elk:
|
||||
driver: bridge
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
24
docker-compose/ELK/logstash/pipeline/logstash.conf
Normal file
24
docker-compose/ELK/logstash/pipeline/logstash.conf
Normal file
@@ -0,0 +1,24 @@
|
||||
input {
|
||||
beats {
|
||||
port => 5044
|
||||
}
|
||||
}
|
||||
|
||||
filter {
|
||||
# Add your grok/mutate/etc filters here as you get to that lesson.
|
||||
}
|
||||
|
||||
output {
|
||||
elasticsearch {
|
||||
hosts => ["http://elasticsearch:9200"]
|
||||
user => "elastic"
|
||||
password => "${ELASTICSEARCH_PASSWORD}"
|
||||
index => "logstash-%{+YYYY.MM.dd}"
|
||||
}
|
||||
|
||||
# Comment this out once you trust the pipeline — it's just here so you
|
||||
# can see events flowing while you're learning.
|
||||
stdout {
|
||||
codec => rubydebug
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,28 @@
|
||||
services:
|
||||
semaphore:
|
||||
image: semaphoreui/semaphore:latest
|
||||
container_name: semaphore
|
||||
ports:
|
||||
- "3001:3000"
|
||||
environment:
|
||||
SEMAPHORE_DB_DIALECT: sqlite
|
||||
SEMAPHORE_DB_PATH: /var/lib/semaphore/database.db
|
||||
SEMAPHORE_ADMIN: admin
|
||||
SEMAPHORE_ADMIN_PASSWORD: rezayaghobi1386
|
||||
SEMAPHORE_ADMIN_EMAIL: rezay5639@gmail.com
|
||||
SEMAPHORE_ADMIN_NAME: Admin
|
||||
SEMAPHORE_SECRET_KEY: cc4b3a5a90d31b963bee48a9dee089472bef5e0461f934db7891fc68161d36f7
|
||||
dockscope:
|
||||
image: ghcr.io/manuelr-t/dockscope:latest
|
||||
container_name: dockscope
|
||||
volumes:
|
||||
- ./data:/var/lib/semaphore
|
||||
- ./playbooks:/playbooks
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
# HTTPS
|
||||
- "traefik.http.routers.dockscope.rule=Host(`dockscope.home.server`)"
|
||||
- "traefik.http.routers.dockscope.entrypoints=websecure"
|
||||
- "traefik.http.routers.dockscope.tls=true"
|
||||
- "traefik.http.routers.dockscope.service=dockscope"
|
||||
- "traefik.http.routers.dockscope.middlewares=security-headers@file,dockscope-auth@file"
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.dockscope-http.rule=Host(`dockscope.home.server`)"
|
||||
- "traefik.http.routers.dockscope-http.entrypoints=web"
|
||||
- "traefik.http.routers.dockscope-http.middlewares=redirect-to-https@file"
|
||||
# Internal port
|
||||
- "traefik.http.services.dockscope.loadbalancer.server.port=4681"
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
@@ -2,18 +2,41 @@ services:
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:latest
|
||||
container_name: filebrowser
|
||||
ports:
|
||||
- "8085:80"
|
||||
restart: unless-stopped
|
||||
user: "1000:1000"
|
||||
ports:
|
||||
- "8085:80" # keep for now (fallback)
|
||||
volumes:
|
||||
- /srv/samba/games:/games
|
||||
- /mnt/storage:/storage
|
||||
- /home/rezayaghobi/docker/filebrowser/config:/config
|
||||
- filebrowser_db:/database
|
||||
- filebrowser_srv:/srv
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# HTTPS
|
||||
- "traefik.http.routers.filebrowser.rule=Host(`filebrowser.home.server`)"
|
||||
- "traefik.http.routers.filebrowser.entrypoints=websecure"
|
||||
- "traefik.http.routers.filebrowser.tls=true"
|
||||
- "traefik.http.routers.filebrowser.service=filebrowser"
|
||||
- "traefik.http.routers.filebrowser.middlewares=security-headers@file"
|
||||
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.filebrowser-http.rule=Host(`filebrowser.home.server`)"
|
||||
- "traefik.http.routers.filebrowser-http.entrypoints=web"
|
||||
- "traefik.http.routers.filebrowser-http.middlewares=redirect-to-https@file"
|
||||
|
||||
# Internal port (Filebrowser listens on 80 inside the container)
|
||||
- "traefik.http.services.filebrowser.loadbalancer.server.port=80"
|
||||
|
||||
volumes:
|
||||
filebrowser_db:
|
||||
filebrowser_srv:
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
@@ -5,9 +5,7 @@ services:
|
||||
restart: unless-stopped
|
||||
hostname: gitlab.local
|
||||
ports:
|
||||
- "8929:80"
|
||||
- "2224:22"
|
||||
|
||||
volumes:
|
||||
- ./config:/etc/gitlab
|
||||
- ./logs:/var/log/gitlab
|
||||
@@ -16,10 +14,14 @@ services:
|
||||
environment:
|
||||
GITLAB_OMNIBUS_CONFIG: |
|
||||
gitlab_rails['monitoring_whitelist'] = ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']
|
||||
external_url 'http://192.168.100.6:8929'
|
||||
external_url 'https://gitlab.home.server'
|
||||
|
||||
nginx['listen_port'] = 80
|
||||
nginx['listen_https'] = false
|
||||
nginx['proxy_set_headers'] = {
|
||||
"X-Forwarded-Proto" => "https",
|
||||
"X-Forwarded-Ssl" => "on"
|
||||
}
|
||||
|
||||
gitlab_rails['gitlab_shell_ssh_port'] = 2224
|
||||
|
||||
@@ -38,14 +40,52 @@ services:
|
||||
postgres_exporter['enable'] = false
|
||||
gitlab_exporter['enable'] = false
|
||||
|
||||
# Registry off for now — flip to true later if you want it
|
||||
registry['enable'] = false
|
||||
# Registry
|
||||
registry['enable'] = true
|
||||
registry_external_url 'https://registry.home.server'
|
||||
gitlab_rails['registry_enabled'] = true
|
||||
gitlab_rails['registry_host'] = "registry.home.server"
|
||||
gitlab_rails['registry_port'] = "443"
|
||||
registry_nginx['listen_port'] = 5050
|
||||
registry_nginx['listen_https'] = false
|
||||
networks:
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# --- Web UI ---
|
||||
- "traefik.http.routers.gitlab.rule=Host(`gitlab.home.server`)"
|
||||
- "traefik.http.routers.gitlab.entrypoints=websecure"
|
||||
- "traefik.http.routers.gitlab.tls=true"
|
||||
- "traefik.http.routers.gitlab.service=gitlab"
|
||||
- "traefik.http.routers.gitlab.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.gitlab-http.rule=Host(`gitlab.home.server`)"
|
||||
- "traefik.http.routers.gitlab-http.entrypoints=web"
|
||||
- "traefik.http.routers.gitlab-http.service=gitlab"
|
||||
- "traefik.http.routers.gitlab-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.gitlab.loadbalancer.server.port=80"
|
||||
|
||||
# --- Container Registry ---
|
||||
- "traefik.http.routers.registry.rule=Host(`registry.home.server`)"
|
||||
- "traefik.http.routers.registry.entrypoints=websecure"
|
||||
- "traefik.http.routers.registry.tls=true"
|
||||
- "traefik.http.routers.registry.service=registry"
|
||||
- "traefik.http.routers.registry.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.registry-http.rule=Host(`registry.home.server`)"
|
||||
- "traefik.http.routers.registry-http.entrypoints=web"
|
||||
- "traefik.http.routers.registry-http.service=registry"
|
||||
- "traefik.http.routers.registry-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.registry.loadbalancer.server.port=5050"
|
||||
|
||||
gitlab-runner:
|
||||
image: gitlab/gitlab-runner:latest
|
||||
container_name: gitlab-runner
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9252:9252"
|
||||
volumes:
|
||||
- ./runner-config:/etc/gitlab-runner
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
@@ -17,7 +17,25 @@ services:
|
||||
- /mnt/storage/films/downloads:/downloads
|
||||
networks:
|
||||
- mediastack
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
# HTTPS
|
||||
- "traefik.http.routers.qbittorrent.rule=Host(`qbittorrent.home.server`)"
|
||||
- "traefik.http.routers.qbittorrent.entrypoints=websecure"
|
||||
- "traefik.http.routers.qbittorrent.tls=true"
|
||||
- "traefik.http.routers.qbittorrent.service=qbittorrent"
|
||||
- "traefik.http.routers.qbittorrent.middlewares=security-headers@file"
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.qbittorrent-http.rule=Host(`qbittorrent.home.server`)"
|
||||
- "traefik.http.routers.qbittorrent-http.entrypoints=web"
|
||||
- "traefik.http.routers.qbittorrent-http.middlewares=redirect-to-https@file"
|
||||
# Internal port (qbittorrent WebUI listens on 8080 inside the container)
|
||||
- "traefik.http.services.qbittorrent.loadbalancer.server.port=8080"
|
||||
|
||||
networks:
|
||||
mediastack:
|
||||
external: true
|
||||
web_net:
|
||||
external: true
|
||||
@@ -2,11 +2,12 @@ services:
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
|
||||
environment:
|
||||
- TZ=Asia/Tehran
|
||||
- PLEX_CLAIM=claim-uRK14oDz86FsyFgNyaBh
|
||||
|
||||
volumes:
|
||||
- /home/rezayaghobi/docker/plex:/config
|
||||
- /mnt/storage:/media
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -13,7 +13,28 @@ services:
|
||||
- ./config:/config
|
||||
networks:
|
||||
- mediastack
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# HTTPS
|
||||
- "traefik.http.routers.prowlarr.rule=Host(`prowlarr.home.server`)"
|
||||
- "traefik.http.routers.prowlarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.prowlarr.tls=true"
|
||||
- "traefik.http.routers.prowlarr.service=prowlarr"
|
||||
- "traefik.http.routers.prowlarr.middlewares=security-headers@file"
|
||||
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.prowlarr-http.rule=Host(`prowlarr.home.server`)"
|
||||
- "traefik.http.routers.prowlarr-http.entrypoints=web"
|
||||
- "traefik.http.routers.prowlarr-http.middlewares=redirect-to-https@file"
|
||||
|
||||
# Internal port (prowlarr listens on 9696 inside the container)
|
||||
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
|
||||
|
||||
networks:
|
||||
mediastack:
|
||||
external: true
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
@@ -10,13 +10,30 @@ services:
|
||||
- ./config:/config
|
||||
- /mnt/storage/films:/media
|
||||
- /mnt/storage/films/downloads:/downloads
|
||||
|
||||
ports:
|
||||
- 7878:7878
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mediastack
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
# HTTPS
|
||||
- "traefik.http.routers.radarr.rule=Host(`radarr.home.server`)"
|
||||
- "traefik.http.routers.radarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.radarr.tls=true"
|
||||
- "traefik.http.routers.radarr.service=radarr"
|
||||
- "traefik.http.routers.radarr.middlewares=security-headers@file"
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.radarr-http.rule=Host(`radarr.home.server`)"
|
||||
- "traefik.http.routers.radarr-http.entrypoints=web"
|
||||
- "traefik.http.routers.radarr-http.middlewares=redirect-to-https@file"
|
||||
# Internal port (radarr listens on 7878 inside the container)
|
||||
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
|
||||
|
||||
networks:
|
||||
mediastack:
|
||||
external: true
|
||||
web_net:
|
||||
external: true
|
||||
@@ -16,7 +16,29 @@ services:
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mediastack
|
||||
- web_net
|
||||
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# HTTPS
|
||||
- "traefik.http.routers.sonarr.rule=Host(`sonarr.home.server`)"
|
||||
- "traefik.http.routers.sonarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.sonarr.tls=true"
|
||||
- "traefik.http.routers.sonarr.service=sonarr"
|
||||
- "traefik.http.routers.sonarr.middlewares=security-headers@file"
|
||||
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.sonarr-http.rule=Host(`sonarr.home.server`)"
|
||||
- "traefik.http.routers.sonarr-http.entrypoints=web"
|
||||
- "traefik.http.routers.sonarr-http.middlewares=redirect-to-https@file"
|
||||
|
||||
# Internal port (Filebrowser listens on 8989 inside the container)
|
||||
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
|
||||
|
||||
networks:
|
||||
mediastack:
|
||||
external: true
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
@@ -15,10 +15,27 @@ services:
|
||||
- /mnt/storage/films/downloads:/downloads
|
||||
- /mnt/storage/adult:/data/whisparr
|
||||
- /mnt/storage/films/downloads/whisparr:/downloads/whisparr # ADD THIS
|
||||
|
||||
networks:
|
||||
- mediastack
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
# HTTPS
|
||||
- "traefik.http.routers.whisparr.rule=Host(`whisparr.home.server`)"
|
||||
- "traefik.http.routers.whisparr.entrypoints=websecure"
|
||||
- "traefik.http.routers.whisparr.tls=true"
|
||||
- "traefik.http.routers.whisparr.service=whisparr"
|
||||
- "traefik.http.routers.whisparr.middlewares=security-headers@file"
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.whisparr-http.rule=Host(`whisparr.home.server`)"
|
||||
- "traefik.http.routers.whisparr-http.entrypoints=web"
|
||||
- "traefik.http.routers.whisparr-http.middlewares=redirect-to-https@file"
|
||||
# Internal port (whisparr listens on 6969 inside the container)
|
||||
- "traefik.http.services.whisparr.loadbalancer.server.port=6969"
|
||||
|
||||
networks:
|
||||
mediastack:
|
||||
external: true
|
||||
web_net:
|
||||
external: true
|
||||
@@ -3,13 +3,54 @@ services:
|
||||
image: minio/minio:RELEASE.2024-01-18T22-51-28Z-cpuv1
|
||||
container_name: minio
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9500:9000"
|
||||
- "9501:9001"
|
||||
|
||||
environment:
|
||||
MINIO_ROOT_USER: admin
|
||||
MINIO_ROOT_PASSWORD: change this
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
MINIO_BROWSER_REDIRECT_URL: https://minio.home.server
|
||||
|
||||
volumes:
|
||||
- ./data:/data
|
||||
|
||||
command: server /data --console-address ":9001"
|
||||
|
||||
mem_limit: 256m
|
||||
|
||||
networks:
|
||||
- web_net
|
||||
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# Console
|
||||
- "traefik.http.routers.minio-console.rule=Host(`minio.home.server`)"
|
||||
- "traefik.http.routers.minio-console.entrypoints=websecure"
|
||||
- "traefik.http.routers.minio-console.tls=true"
|
||||
- "traefik.http.routers.minio-console.service=minio-console"
|
||||
- "traefik.http.routers.minio-console.middlewares=security-headers@file"
|
||||
- "traefik.http.services.minio-console.loadbalancer.server.port=9001"
|
||||
|
||||
# HTTP -> HTTPS
|
||||
- "traefik.http.routers.minio-console-http.rule=Host(`minio.home.server`)"
|
||||
- "traefik.http.routers.minio-console-http.entrypoints=web"
|
||||
- "traefik.http.routers.minio-console-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.minio-console-http.service=minio-console"
|
||||
|
||||
# S3 API
|
||||
- "traefik.http.routers.minio-api.rule=Host(`s3.home.server`)"
|
||||
- "traefik.http.routers.minio-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.minio-api.tls=true"
|
||||
- "traefik.http.routers.minio-api.service=minio-api"
|
||||
- "traefik.http.routers.minio-api.middlewares=security-headers@file"
|
||||
- "traefik.http.services.minio-api.loadbalancer.server.port=9000"
|
||||
|
||||
# HTTP -> HTTPS
|
||||
- "traefik.http.routers.minio-api-http.rule=Host(`s3.home.server`)"
|
||||
- "traefik.http.routers.minio-api-http.entrypoints=web"
|
||||
- "traefik.http.routers.minio-api-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.minio-api-http.service=minio-api"
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
container_name: prometheus
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9090:9090"
|
||||
- "9090:9090" # keep for now
|
||||
volumes:
|
||||
- ./prometheus:/etc/prometheus:ro
|
||||
- ./data/prometheus:/prometheus
|
||||
@@ -14,18 +14,44 @@ services:
|
||||
- "--web.enable-lifecycle"
|
||||
networks:
|
||||
- monitoring
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.prometheus.rule=Host(`prometheus.home.server`)"
|
||||
- "traefik.http.routers.prometheus.entrypoints=websecure"
|
||||
- "traefik.http.routers.prometheus.tls=true"
|
||||
- "traefik.http.routers.prometheus.service=prometheus"
|
||||
- "traefik.http.routers.prometheus.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.prometheus-http.rule=Host(`prometheus.home.server`)"
|
||||
- "traefik.http.routers.prometheus-http.entrypoints=web"
|
||||
- "traefik.http.routers.prometheus-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: grafana
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3000:3000" # keep for now
|
||||
volumes:
|
||||
- ./grafana/data:/var/lib/grafana
|
||||
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
|
||||
networks:
|
||||
- monitoring
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.grafana.rule=Host(`grafana.home.server`)"
|
||||
- "traefik.http.routers.grafana.entrypoints=websecure"
|
||||
- "traefik.http.routers.grafana.tls=true"
|
||||
- "traefik.http.routers.grafana.service=grafana"
|
||||
- "traefik.http.routers.grafana.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.grafana-http.rule=Host(`grafana.home.server`)"
|
||||
- "traefik.http.routers.grafana-http.entrypoints=web"
|
||||
- "traefik.http.routers.grafana-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
|
||||
|
||||
node_exporter:
|
||||
image: prom/node-exporter:latest
|
||||
@@ -39,13 +65,14 @@ services:
|
||||
- /:/rootfs:ro
|
||||
networks:
|
||||
- monitoring
|
||||
# no Traefik labels – no web UI needed
|
||||
|
||||
cadvisor:
|
||||
image: ghcr.io/google/cadvisor:latest
|
||||
container_name: cadvisor
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "8080:8080" # keep for now
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:rw
|
||||
@@ -53,6 +80,19 @@ services:
|
||||
- /var/lib/docker/:/var/lib/docker:ro
|
||||
networks:
|
||||
- monitoring
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.cadvisor.rule=Host(`cadvisor.home.server`)"
|
||||
- "traefik.http.routers.cadvisor.entrypoints=websecure"
|
||||
- "traefik.http.routers.cadvisor.tls=true"
|
||||
- "traefik.http.routers.cadvisor.service=cadvisor"
|
||||
- "traefik.http.routers.cadvisor.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.cadvisor-http.rule=Host(`cadvisor.home.server`)"
|
||||
- "traefik.http.routers.cadvisor-http.entrypoints=web"
|
||||
- "traefik.http.routers.cadvisor-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.cadvisor.loadbalancer.server.port=8080"
|
||||
|
||||
loki:
|
||||
image: grafana/loki:3.7.3
|
||||
@@ -62,12 +102,25 @@ services:
|
||||
ports:
|
||||
- "3100:3100"
|
||||
volumes:
|
||||
- ./loki/local-config.yaml:/etc/loki/local-config.yaml:ro
|
||||
- ./loki/local-config.yaml:/etc/loki/local-config.yaml:ro
|
||||
- ./data/loki:/loki
|
||||
mem_limit: 400m
|
||||
cpus: 1
|
||||
networks:
|
||||
- monitoring
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.loki.rule=Host(`loki.home.server`)"
|
||||
- "traefik.http.routers.loki.entrypoints=websecure"
|
||||
- "traefik.http.routers.loki.tls=true"
|
||||
- "traefik.http.routers.loki.service=loki"
|
||||
- "traefik.http.routers.loki.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.loki-http.rule=Host(`loki.home.server`)"
|
||||
- "traefik.http.routers.loki-http.entrypoints=web"
|
||||
- "traefik.http.routers.loki-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.loki.loadbalancer.server.port=3100"
|
||||
|
||||
alloy:
|
||||
image: grafana/alloy:v1.17.1
|
||||
@@ -91,7 +144,22 @@ services:
|
||||
- loki
|
||||
networks:
|
||||
- monitoring
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.alloy.rule=Host(`alloy.home.server`)"
|
||||
- "traefik.http.routers.alloy.entrypoints=websecure"
|
||||
- "traefik.http.routers.alloy.tls=true"
|
||||
- "traefik.http.routers.alloy.service=alloy"
|
||||
- "traefik.http.routers.alloy.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.alloy-http.rule=Host(`alloy.home.server`)"
|
||||
- "traefik.http.routers.alloy-http.entrypoints=web"
|
||||
- "traefik.http.routers.alloy-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.alloy.loadbalancer.server.port=12345"
|
||||
|
||||
networks:
|
||||
monitoring:
|
||||
driver: bridge
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
@@ -36,23 +36,54 @@ services:
|
||||
DB_POSTGRESDB_USER: ${POSTGRES_USER}
|
||||
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
NPM_ADMIN_EMAIL: ${NPM_ADMIN_EMAIL}
|
||||
NPM_ADMIN_PASSWORD: ${NPM_ADMIN_PASSWORD}
|
||||
PIHOLE_PASSWORD: ${PIHOLE_PASSWORD}
|
||||
|
||||
N8N_BASIC_AUTH_ACTIVE: true
|
||||
N8N_BLOCK_ENV_ACCESS_IN_NODE: false
|
||||
N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
|
||||
N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}
|
||||
|
||||
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
|
||||
|
||||
N8N_SECURE_COOKIE: false
|
||||
N8N_HOST: ${N8N_HOST}
|
||||
N8N_HOST: n8n.home.server
|
||||
N8N_PROTOCOL: https
|
||||
N8N_PORT: 5678
|
||||
N8N_PROTOCOL: http
|
||||
WEBHOOK_URL: https://n8n.home.server/
|
||||
|
||||
TZ: ${TZ}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# HTTPS router
|
||||
- "traefik.http.routers.n8n.rule=Host(`n8n.home.server`)"
|
||||
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||
- "traefik.http.routers.n8n.tls=true"
|
||||
- "traefik.http.routers.n8n.service=n8n"
|
||||
- "traefik.http.routers.n8n.middlewares=security-headers@file"
|
||||
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.n8n-http.rule=Host(`n8n.home.server`)"
|
||||
- "traefik.http.routers.n8n-http.entrypoints=web"
|
||||
- "traefik.http.routers.n8n-http.middlewares=redirect-to-https@file"
|
||||
|
||||
# Internal container port
|
||||
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||
|
||||
ports:
|
||||
- "5678:5678"
|
||||
|
||||
networks:
|
||||
- default # keep the existing n8n_default (for postgres)
|
||||
- web_net # new
|
||||
|
||||
volumes:
|
||||
- ./n8n-data:/home/node/.n8n
|
||||
- /mnt/storage:/mnt/storage
|
||||
- /mnt/windows-video:/mnt/windows-video
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
@@ -10,14 +10,19 @@ services:
|
||||
- ./projects:/home/openhands/workspace/project
|
||||
stdin_open: true
|
||||
tty: true
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
container_name: ollama
|
||||
depends_on:
|
||||
- github-mcp
|
||||
|
||||
github-mcp:
|
||||
image: ghcr.io/github/github-mcp-server:latest
|
||||
container_name: github-mcp
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "11434:11434"
|
||||
volumes:
|
||||
- ollama-data:/root/.ollama
|
||||
- "8083:8083"
|
||||
stdin_open: true
|
||||
tty: true
|
||||
environment:
|
||||
- GITHUB_PERSONAL_ACCESS_TOKEN=${GITHUB_PERSONAL_ACCESS_TOKEN}
|
||||
|
||||
volumes:
|
||||
openhands-data:
|
||||
ollama-data:
|
||||
@@ -1 +1 @@
|
||||
PIHOLE_WEBPASSWORD=change-me
|
||||
PIHOLE_WEBPASSWORD=CHANGE_ME
|
||||
|
||||
@@ -3,11 +3,10 @@ services:
|
||||
container_name: pihole
|
||||
image: ghcr.io/pi-hole/pihole:latest
|
||||
network_mode: host
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: "Asia/Tehran"
|
||||
WEBPASSWORD: ${PIHOLE_WEBPASSWORD}
|
||||
FTLCONF_webserver_port: '8082o,[::]:8082o'
|
||||
volumes:
|
||||
- ./etc-pihole:/etc/pihole
|
||||
- ./etc-dnsmasq.d:/etc/dnsmasq.d
|
||||
|
||||
@@ -4,7 +4,31 @@ services:
|
||||
container_name: portainer
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9000" # keep for now (fallback)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /home/rezayaghobi/docker/portainer/data:/data
|
||||
networks:
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# HTTPS
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.home.server`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
- "traefik.http.routers.portainer.tls=true"
|
||||
- "traefik.http.routers.portainer.service=portainer"
|
||||
- "traefik.http.routers.portainer.middlewares=security-headers@file"
|
||||
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.portainer-http.rule=Host(`portainer.home.server`)"
|
||||
- "traefik.http.routers.portainer-http.entrypoints=web"
|
||||
- "traefik.http.routers.portainer-http.middlewares=redirect-to-https@file"
|
||||
|
||||
# Internal port (Portainer listens on 9000 inside the container)
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
3
docker-compose/semaphore/.env.example
Normal file
3
docker-compose/semaphore/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
SEMAPHORE_ADMIN_PASSWORD=CHANGE_ME
|
||||
SEMAPHORE_ADMIN_EMAIL=your-email@example.com
|
||||
SEMAPHORE_SECRET_KEY=CHANGE_ME_generate_with_openssl_rand_hex_32
|
||||
@@ -8,10 +8,32 @@ services:
|
||||
SEMAPHORE_DB_DIALECT: sqlite
|
||||
SEMAPHORE_DB_PATH: /var/lib/semaphore/database.db
|
||||
SEMAPHORE_ADMIN: admin
|
||||
SEMAPHORE_ADMIN_PASSWORD: choose a passwor or use .env (better )
|
||||
SEMAPHORE_ADMIN_EMAIL: rezay5639@gmail.com
|
||||
SEMAPHORE_ADMIN_PASSWORD: ${SEMAPHORE_ADMIN_PASSWORD}
|
||||
SEMAPHORE_ADMIN_EMAIL: ${SEMAPHORE_ADMIN_EMAIL}
|
||||
SEMAPHORE_ADMIN_NAME: Admin
|
||||
SEMAPHORE_SECRET_KEY: <your-long-secret-key>
|
||||
SEMAPHORE_SECRET_KEY: ${SEMAPHORE_SECRET_KEY}
|
||||
volumes:
|
||||
- ./data:/var/lib/semaphore
|
||||
- ./playbooks:/playbooks
|
||||
networks:
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
# HTTPS
|
||||
- "traefik.http.routers.semaphore.rule=Host(`semaphore.home.server`)"
|
||||
- "traefik.http.routers.semaphore.entrypoints=websecure"
|
||||
- "traefik.http.routers.semaphore.tls=true"
|
||||
- "traefik.http.routers.semaphore.service=semaphore"
|
||||
- "traefik.http.routers.semaphore.middlewares=security-headers@file"
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.semaphore-http.rule=Host(`semaphore.home.server`)"
|
||||
- "traefik.http.routers.semaphore-http.entrypoints=web"
|
||||
- "traefik.http.routers.semaphore-http.middlewares=redirect-to-https@file"
|
||||
# Internal port (semaphore listens on 3000 inside the container)
|
||||
- "traefik.http.services.semaphore.loadbalancer.server.port=3000"
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
|
||||
@@ -6,7 +6,5 @@ services:
|
||||
volumes:
|
||||
- /dev:/dev
|
||||
- /run/udev:/run/udev:ro
|
||||
ports:
|
||||
- "8090:8090"
|
||||
restart: unless-stopped
|
||||
|
||||
- ./report:/etc/hdsentinel
|
||||
restart: unless-stopped
|
||||
134
docker-compose/traefik/README.md
Normal file
134
docker-compose/traefik/README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Traefik
|
||||
|
||||
Self-hosted reverse proxy replacing Nginx Proxy Manager — gives every service a friendly `<service>.home.server` hostname with locally-trusted HTTPS, configured entirely as code.
|
||||
|
||||
## Why the migration from Nginx Proxy Manager
|
||||
|
||||
NPM works fine, but the switching cost grew as the homelab did. Two things pushed the move:
|
||||
|
||||
### 1. Infrastructure as code
|
||||
|
||||
NPM's entire configuration lives behind a web UI — proxy hosts, SSL certificates, custom locations, access rules. None of it is version-controlled, exportable, or reproducible. If the container dies and comes back without its data volume, you're rebuilding every proxy host by hand.
|
||||
|
||||
Traefik is the opposite: every route, middleware, TLS cert, and entrypoint is defined in YAML files that live in this repo. The compose file, the `dynamic/` configs, the cert files — all version-controlled, all diffable, all reproducible with `docker compose up -d`. This is the same pattern the rest of the homelab already follows (each service is a Compose file in a folder), and Traefik fits that model naturally where NPM didn't.
|
||||
|
||||
### 2. Docker daemon auto-discovery
|
||||
|
||||
NPM requires you to manually add each proxy host through its web UI — type the domain, point it at the container's IP and port, attach an SSL cert, save. Every new service means another round-trip through the UI.
|
||||
|
||||
Traefik talks directly to the Docker daemon and watches for containers with `traefik.enable=true`. Add a few labels to any compose file and Traefik picks up the new route automatically — no UI interaction, no restart, no manual configuration. This means adding a new service to the reverse proxy is a two-line addition to that service's compose file, not a separate step in a different tool.
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
traefik/
|
||||
├── compose.yaml # Traefik container + static config
|
||||
├── certs/ # Local TLS certificates (mkcert)
|
||||
│ ├── home.server.crt
|
||||
│ └── home.server.key
|
||||
└── dynamic/ # File-based provider — routes, middlewares, TLS
|
||||
├── tls.yaml # Default TLS cert store
|
||||
├── middlewares.yaml # HTTPS redirect + security headers
|
||||
├── pihole.yaml # Pi-hole route
|
||||
├── plex.yml # Plex route (external host)
|
||||
├── sentinel.yml # Sentinel route (external host)
|
||||
└── dockscope-auth.yml # Basic auth middleware for DockScope
|
||||
```
|
||||
|
||||
### Static config (`compose.yaml`)
|
||||
|
||||
Traefik's static configuration is passed as command-line flags:
|
||||
|
||||
- **Docker provider** — watches the Docker daemon for labeled containers on the `web_net` network
|
||||
- **File provider** — watches `/dynamic/` for YAML config changes (hot-reloaded, no restart needed)
|
||||
- **Entrypoints** — `:80` (HTTP) and `:443` (HTTPS)
|
||||
- **API dashboard** — enabled but not insecure (accessed via its own HTTPS route)
|
||||
|
||||
### Dynamic config (`dynamic/`)
|
||||
|
||||
Routes and middlewares are defined as YAML files, not labels. This keeps the compose files clean and makes it easy to see all routing rules in one place:
|
||||
|
||||
- **`middlewares.yaml`** — shared `redirect-to-https` and `security-headers` middlewares (HSTS, XSS filter, content-type nosniff, SAMEORIGIN framing)
|
||||
- **`tls.yaml`** — default TLS cert store pointing at the mkcert-generated certificate
|
||||
- **`pihole.yaml`**, **`plex.yml`**, **`sentinel.yml`** — per-service route definitions for services that can't use Docker labels (host-network or external hosts)
|
||||
|
||||
### Services using Docker labels
|
||||
|
||||
Most services define their Traefik routes directly in their compose files via labels:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
- "traefik.http.routers.<service>.rule=Host(`<service>.home.server`)"
|
||||
- "traefik.http.routers.<service>.entrypoints=websecure"
|
||||
- "traefik.http.routers.<service>.tls=true"
|
||||
- "traefik.http.routers.<service>.middlewares=security-headers@file"
|
||||
- "traefik.http.routers.<service>-http.rule=Host(`<service>.home.server`)"
|
||||
- "traefik.http.routers.<service>-http.entrypoints=web"
|
||||
- "traefik.http.routers.<service>-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.services.<service>.loadbalancer.server.port=<internal-port>"
|
||||
```
|
||||
|
||||
Services currently fronted by Traefik:
|
||||
|
||||
| Service | Hostname | Internal Port |
|
||||
|---|---|---|
|
||||
| Prometheus | `prometheus.home.server` | 9090 |
|
||||
| Grafana | `grafana.home.server` | 3000 |
|
||||
| cAdvisor | `cadvisor.home.server` | 8080 |
|
||||
| Loki | `loki.home.server` | 3100 |
|
||||
| Alloy | `alloy.home.server` | 12345 |
|
||||
| Pi-hole | `pihole.home.server` | 8082 (host) |
|
||||
| Portainer | `portainer.home.server` | 9000 |
|
||||
| n8n | `n8n.home.server` | 5678 |
|
||||
| Filebrowser | `filebrowser.home.server` | 80 |
|
||||
| DockScope | `dockscope.home.server` | 4681 |
|
||||
| Semaphore | `semaphore.home.server` | 3000 |
|
||||
| MinIO | `minio.home.server` | 9001 |
|
||||
| Plex | `plex.home.server` | 32400 (external) |
|
||||
| Sentinel | `sentinel.home.server` | 8088 (external) |
|
||||
| Vaultwarden | `vaultwarden.home.server` | 80 |
|
||||
| Traefik Dashboard | `traefik.home.server` | API@internal |
|
||||
|
||||
## TLS certificates
|
||||
|
||||
Same approach as before — [mkcert](https://github.com/FiloSottile/mkcert) generates a local CA and signs certificates for `*.home.server`. The cert and key live in `certs/` and are mounted read-only into the Traefik container.
|
||||
|
||||
Traefik loads the default cert via `dynamic/tls.yaml`:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
stores:
|
||||
default:
|
||||
defaultCertificate:
|
||||
certFile: /certs/home.server.crt
|
||||
keyFile: /certs/home.server.key
|
||||
```
|
||||
|
||||
Adding a new service with a `.home.server` hostname automatically gets the right cert — no per-host certificate assignment needed.
|
||||
|
||||
## Adding a new service
|
||||
|
||||
1. **If the service is a Docker container on `web_net`** — add Traefik labels to its compose file (copy the pattern from any existing service above)
|
||||
2. **If the service is host-network or external** — create a new YAML file in `dynamic/` with the router and service definition
|
||||
3. **Add the hostname to Pi-hole** — so DNS resolves `<service>.home.server` to the server's LAN IP
|
||||
|
||||
No restart required — Traefik watches both the Docker daemon and the `dynamic/` directory and picks up changes automatically.
|
||||
|
||||
## Network
|
||||
|
||||
All Traefik-fronted services join the `web_net` external Docker network. This network must be created before starting Traefik:
|
||||
|
||||
```bash
|
||||
docker network create web_net
|
||||
```
|
||||
|
||||
## Security notes
|
||||
|
||||
- The Traefik dashboard is behind its own HTTPS route with security headers — not exposed on the public internet, LAN-only via Pi-hole DNS
|
||||
- DockScope additionally uses a basicAuth middleware (`dynamic/dockscope-auth.yml`) since it mounts the Docker socket
|
||||
- All HTTP requests are redirected to HTTPS permanently (301)
|
||||
- Security headers include HSTS (1 year, includeSubdomains, preload), XSS filter, content-type nosniff, and SAMEORIGIN framing
|
||||
|
||||
[← Back to Home](../../docs/Home.md)
|
||||
25
docker-compose/traefik/certs/home.server.crt
Normal file
25
docker-compose/traefik/certs/home.server.crt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEQTCCAqmgAwIBAgIQMHgxq9We6lR+AyMJeJHo7DANBgkqhkiG9w0BAQsFADB3
|
||||
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExJjAkBgNVBAsMHXJlemF5
|
||||
YWdob2JpQGhvbWVzZXJ2ZXIgKHJlemEpMS0wKwYDVQQDDCRta2NlcnQgcmV6YXlh
|
||||
Z2hvYmlAaG9tZXNlcnZlciAocmV6YSkwHhcNMjYwODI0MDgzODU5WhcNMjgxMTI0
|
||||
MDgzODU5WjBRMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNh
|
||||
dGUxJjAkBgNVBAsMHXJlemF5YWdob2JpQGhvbWVzZXJ2ZXIgKHJlemEpMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvgzUWThVUpVqdqkoJ8325vsC5Eoe
|
||||
mNlihoZ+zXenBBpZgvuiMdz+duBGPdWQcXK7DQr0jBXNqx0wt8w4RSg9pBETPNXT
|
||||
mcRxudzzvKggVVzUqJ6MrODj2NSNCxUL05oR/iH30+pmliCRE3CyjEvlnpcUVdu1
|
||||
Qwp0LdxC4fR2ZNbWQbRZ3aoQXYwaQ7WaFno9tlyhvhBdh/rtQOMhi6kVdtB8pTGY
|
||||
Zzc1fSBJsOCqlwyN6jcp/J8jZ3AfCU1fdqZwMXuQd0TbUOv/K1grQ99IkzJu2zKJ
|
||||
jKomBdD1SZnn+/buUl2KGAl1e/G6/LIDgoKVDjb7y4XNCcZX4oNx7pxPdwIDAQAB
|
||||
o28wbTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0j
|
||||
BBgwFoAUuWL4Syj7LA309BRwMJjRz3eC+FUwJQYDVR0RBB4wHIINKi5ob21lLnNl
|
||||
cnZlcoILaG9tZS5zZXJ2ZXIwDQYJKoZIhvcNAQELBQADggGBAKmBR+udJASNvgtN
|
||||
LtQzHcq790hnnVg/O1dbSd0Ah2PSrz/OtYOVMgOTa9WPIYeMjVKVOsRcPlR8lO7O
|
||||
udJuDUi3QkSQ+hmsqFKqC9Mt9uPg6ozcGNcX8/jC/eTIJKYlQCdI69brehR/R4rd
|
||||
xQo+1MYv/JUULUy/1Ix6uGs80jNuVGwwEnfTxU0K47FUI7hx5HjR8wHv0cMY3Fb9
|
||||
tUEJJfWjyuyq+oXB3x8loEEBL7ESQUb0pCKKmtmclerm0ia1X5Sh2Sn+d/Me3RHC
|
||||
ABh0zwQcmC0j/r4T5ajTr5ZK5jzo+3NTtqpLF0TiKAPioGr+W9sJlw5IZhlIVpN8
|
||||
UqNrwvUbyiVuNUrl70QabO0G3PFUX/Kfi1tXh+9eletFpz3CBQrrJljOFoXhzVFt
|
||||
FZGPiQuKkAryDvdMpS5oRnOcgR/hROIkz6ACkECwvvbWE5wb7BX+9NZm39gpNe8r
|
||||
5Cj1OyF6+HZ+o5NHOKzcj4Ci0/e0m+I4Fh9kG5ZXC5+cX79z/A==
|
||||
-----END CERTIFICATE-----
|
||||
56
docker-compose/traefik/compose.yaml
Normal file
56
docker-compose/traefik/compose.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.7
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
|
||||
command:
|
||||
- --log.level=INFO
|
||||
|
||||
# API / dashboard
|
||||
- --api.dashboard=true
|
||||
- --api.insecure=false
|
||||
|
||||
# Docker provider
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.docker.network=web_net
|
||||
- --providers.file.directory=/dynamic
|
||||
- --providers.file.watch=true
|
||||
|
||||
# Entrypoints
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
|
||||
# Health check
|
||||
- --ping=true
|
||||
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./dynamic:/dynamic:ro
|
||||
- ./certs:/certs:ro
|
||||
networks:
|
||||
- web_net
|
||||
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
|
||||
# Dashboard HTTPS
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.home.server`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls=true"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.middlewares=security-headers@file"
|
||||
|
||||
# Dashboard HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.traefik-http.rule=Host(`traefik.home.server`)"
|
||||
- "traefik.http.routers.traefik-http.entrypoints=web"
|
||||
- "traefik.http.routers.traefik-http.middlewares=redirect-to-https@file"
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
7
docker-compose/traefik/dynamic/dockscope-auth.yml
Normal file
7
docker-compose/traefik/dynamic/dockscope-auth.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
http:
|
||||
middlewares:
|
||||
dockscope-auth:
|
||||
basicAuth:
|
||||
users:
|
||||
# Generate with: htpasswd -nB <username>
|
||||
- "${DOCKSCOPE_AUTH_USER}:${DOCKSCOPE_AUTH_HASH}"
|
||||
17
docker-compose/traefik/dynamic/middlewares.yaml
Normal file
17
docker-compose/traefik/dynamic/middlewares.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
http:
|
||||
middlewares:
|
||||
redirect-to-https:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
|
||||
# Optional but recommended security headers (we can tune later)
|
||||
security-headers:
|
||||
headers:
|
||||
browserXssFilter: true
|
||||
contentTypeNosniff: true
|
||||
forceSTSHeader: true
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
stsSeconds: 31536000
|
||||
customFrameOptionsValue: "SAMEORIGIN"
|
||||
24
docker-compose/traefik/dynamic/pihole.yaml
Normal file
24
docker-compose/traefik/dynamic/pihole.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
http:
|
||||
routers:
|
||||
pihole:
|
||||
rule: "Host(`pihole.home.server`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls: {}
|
||||
service: pihole
|
||||
middlewares:
|
||||
- security-headers@file
|
||||
|
||||
pihole-http:
|
||||
rule: "Host(`pihole.home.server`)"
|
||||
entryPoints:
|
||||
- web
|
||||
service: pihole # ← added (required)
|
||||
middlewares:
|
||||
- redirect-to-https@file
|
||||
|
||||
services:
|
||||
pihole:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://172.17.0.1:8082"
|
||||
24
docker-compose/traefik/dynamic/plex.yml
Normal file
24
docker-compose/traefik/dynamic/plex.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
http:
|
||||
routers:
|
||||
plex:
|
||||
rule: "Host(`plex.home.server`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls: {}
|
||||
service: plex
|
||||
middlewares:
|
||||
- security-headers@file
|
||||
|
||||
plex-http:
|
||||
rule: "Host(`plex.home.server`)"
|
||||
entryPoints:
|
||||
- web
|
||||
service: plex
|
||||
middlewares:
|
||||
- redirect-to-https@file
|
||||
|
||||
services:
|
||||
plex:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.100.6:32400"
|
||||
24
docker-compose/traefik/dynamic/sentinel.yml
Normal file
24
docker-compose/traefik/dynamic/sentinel.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
http:
|
||||
routers:
|
||||
sentinel:
|
||||
rule: "Host(`sentinel.home.server`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls: true
|
||||
service: sentinel
|
||||
middlewares:
|
||||
- security-headers@file
|
||||
|
||||
sentinel-http:
|
||||
rule: "Host(`sentinel.home.server`)"
|
||||
entryPoints:
|
||||
- web
|
||||
service: sentinel
|
||||
middlewares:
|
||||
- redirect-to-https@file
|
||||
|
||||
services:
|
||||
sentinel:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.100.6:8088"
|
||||
6
docker-compose/traefik/dynamic/tls.yaml
Normal file
6
docker-compose/traefik/dynamic/tls.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
tls:
|
||||
stores:
|
||||
default:
|
||||
defaultCertificate:
|
||||
certFile: /certs/home.server.crt
|
||||
keyFile: /certs/home.server.key
|
||||
33
docker-compose/vaultwarden/docker-compose.yml
Normal file
33
docker-compose/vaultwarden/docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:latest
|
||||
container_name: vaultwarden
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- WEBSOCKET_ENABLED=true
|
||||
- SIGNUPS_ALLOWED=false # set false after creating your account
|
||||
volumes:
|
||||
- ./vw-data:/data
|
||||
ports:
|
||||
- "8084:80"
|
||||
networks:
|
||||
- web_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web_net"
|
||||
# HTTPS
|
||||
- "traefik.http.routers.vaultwarden.rule=Host(`vaultwarden.home.server`)"
|
||||
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
|
||||
- "traefik.http.routers.vaultwarden.tls=true"
|
||||
- "traefik.http.routers.vaultwarden.service=vaultwarden"
|
||||
- "traefik.http.routers.vaultwarden.middlewares=security-headers@file"
|
||||
# HTTP → HTTPS redirect
|
||||
- "traefik.http.routers.vaultwarden-http.rule=Host(`vaultwarden.home.server`)"
|
||||
- "traefik.http.routers.vaultwarden-http.entrypoints=web"
|
||||
- "traefik.http.routers.vaultwarden-http.middlewares=redirect-to-https@file"
|
||||
# Internal port (vaultwarden listens on 80 inside the container)
|
||||
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
external: true
|
||||
Reference in New Issue
Block a user