Add initial configuration files for deployment and services setup
This commit is contained in:
309
docker-compose.yml
Normal file
309
docker-compose.yml
Normal file
@@ -0,0 +1,309 @@
|
||||
# docker-compose.yml
|
||||
version: "3.9"
|
||||
|
||||
########################
|
||||
# Networks & Volumes
|
||||
########################
|
||||
networks:
|
||||
traefik_proxy:
|
||||
name: traefik_proxy
|
||||
monitoring:
|
||||
name: monitoring
|
||||
internal:
|
||||
name: internal
|
||||
|
||||
volumes:
|
||||
traefik_letsencrypt:
|
||||
traefik_logs:
|
||||
portainer_data:
|
||||
umami_db_data:
|
||||
authelia_data:
|
||||
crowdsec_data:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
uptime_kuma_data:
|
||||
|
||||
########################
|
||||
# Services
|
||||
########################
|
||||
services:
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## Traefik — edge router + ACME (Namecheap DNS)
|
||||
## ─────────────────────────────────────────────
|
||||
traefik:
|
||||
image: traefik:v3.1
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
networks: [traefik_proxy, monitoring]
|
||||
environment:
|
||||
# Namecheap DNS challenge auth
|
||||
NAMECHEAP_API_USER: "${NAMECHEAP_API_USER}"
|
||||
NAMECHEAP_API_KEY: "${NAMECHEAP_API_KEY}"
|
||||
TZ: "${TZ}"
|
||||
command:
|
||||
# Providers
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
|
||||
# Entrypoints
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --entrypoints.websecure.address=:443
|
||||
|
||||
# Dashboard/API (internal)
|
||||
- --api.dashboard=true
|
||||
|
||||
# ACME via DNS-01 (wildcard for *.gate.${DOMAIN})
|
||||
- --certificatesresolvers.le.acme.email=${ACME_EMAIL}
|
||||
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
||||
- --certificatesresolvers.le.acme.dnschallenge=true
|
||||
- --certificatesresolvers.le.acme.dnschallenge.provider=namecheap
|
||||
# Optional: if DNS propagation is slow, uncomment:
|
||||
# - --certificatesresolvers.le.acme.dnschallenge.disablepropagationcheck=true
|
||||
|
||||
# Metrics (Prometheus)
|
||||
- --metrics.prometheus=true
|
||||
- --metrics.prometheus.addrouterslabels=true
|
||||
|
||||
# Logs
|
||||
- --accesslog.filepath=/var/log/traefik/access.log
|
||||
- --accesslog.bufferingsize=100
|
||||
- --log.level=INFO
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- traefik_letsencrypt:/letsencrypt
|
||||
- traefik_logs:/var/log/traefik
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
|
||||
# Reusable security headers
|
||||
- traefik.http.middlewares.security-headers.headers.stsSeconds=31536000
|
||||
- traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true
|
||||
- traefik.http.middlewares.security-headers.headers.stsPreload=true
|
||||
- traefik.http.middlewares.security-headers.headers.browserXssFilter=true
|
||||
- traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true
|
||||
- traefik.http.middlewares.security-headers.headers.referrerPolicy=no-referrer-when-downgrade
|
||||
- traefik.http.middlewares.security-headers.headers.frameDeny=true
|
||||
|
||||
# Authelia ForwardAuth (reusable)
|
||||
- traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.gate.${DOMAIN}
|
||||
- traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true
|
||||
- traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email
|
||||
|
||||
# CrowdSec forward-auth (reusable)
|
||||
- traefik.http.middlewares.crowdsec.forwardauth.address=http://traefik-bouncer:8080/api/v1/forwardAuth
|
||||
- traefik.http.middlewares.crowdsec.forwardauth.trustForwardHeader=true
|
||||
|
||||
# Traefik dashboard (protected)
|
||||
- traefik.http.routers.traefik.rule=Host(`traefik.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.traefik.entrypoints=websecure
|
||||
- traefik.http.routers.traefik.tls.certresolver=le
|
||||
- traefik.http.routers.traefik.service=api@internal
|
||||
- traefik.http.routers.traefik.middlewares=crowdsec,authelia,security-headers
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## Portainer — Docker control plane
|
||||
## ─────────────────────────────────────────────
|
||||
portainer:
|
||||
image: portainer/portainer-ce:latest
|
||||
container_name: portainer
|
||||
restart: unless-stopped
|
||||
networks: [traefik_proxy]
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- portainer_data:/data
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.portainer.rule=Host(`portainer.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.portainer.entrypoints=websecure
|
||||
- traefik.http.routers.portainer.tls.certresolver=le
|
||||
- traefik.http.routers.portainer.middlewares=crowdsec,authelia,security-headers
|
||||
- traefik.http.services.portainer.loadbalancer.server.port=9000
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## Umami + PostgreSQL — privacy analytics
|
||||
## ─────────────────────────────────────────────
|
||||
umami-db:
|
||||
image: postgres:16
|
||||
container_name: umami-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${UMAMI_DB_USER}
|
||||
POSTGRES_PASSWORD: ${UMAMI_DB_PASS}
|
||||
POSTGRES_DB: ${UMAMI_DB_NAME}
|
||||
TZ: "${TZ}"
|
||||
volumes:
|
||||
- umami_db_data:/var/lib/postgresql/data
|
||||
networks: [internal]
|
||||
|
||||
umami:
|
||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
||||
container_name: umami
|
||||
restart: unless-stopped
|
||||
depends_on: [umami-db]
|
||||
environment:
|
||||
DATABASE_URL: postgresql://${UMAMI_DB_USER}:${UMAMI_DB_PASS}@umami-db:5432/${UMAMI_DB_NAME}
|
||||
APP_SECRET: ${UMAMI_APP_SECRET}
|
||||
TRACKER_SCRIPT_NAME: umami
|
||||
TZ: "${TZ}"
|
||||
networks: [traefik_proxy, internal]
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.umami.rule=Host(`umami.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.umami.entrypoints=websecure
|
||||
- traefik.http.routers.umami.tls.certresolver=le
|
||||
- traefik.http.routers.umami.middlewares=crowdsec,authelia,security-headers
|
||||
- traefik.http.services.umami.loadbalancer.server.port=3000
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## Authelia + Redis — SSO/MFA
|
||||
## ─────────────────────────────────────────────
|
||||
authelia:
|
||||
image: authelia/authelia:latest
|
||||
container_name: authelia
|
||||
restart: unless-stopped
|
||||
depends_on: [redis]
|
||||
environment:
|
||||
TZ: "${TZ}"
|
||||
volumes:
|
||||
# Provide your config at ./authelia/configuration.yml
|
||||
- ./authelia/configuration.yml:/config/configuration.yml:ro
|
||||
- authelia_data:/config
|
||||
networks: [traefik_proxy, internal]
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.authelia.rule=Host(`auth.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.authelia.entrypoints=websecure
|
||||
- traefik.http.routers.authelia.tls.certresolver=le
|
||||
- traefik.http.routers.authelia.middlewares=crowdsec,security-headers
|
||||
- traefik.http.services.authelia.loadbalancer.server.port=9091
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: authelia-redis
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## CrowdSec (LAPI) + Traefik bouncer (forwardAuth)
|
||||
## ─────────────────────────────────────────────
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:latest
|
||||
container_name: crowdsec
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: "${TZ}"
|
||||
GID: "0"
|
||||
COLLECTIONS: "crowdsecurity/traefik crowdsecurity/linux"
|
||||
volumes:
|
||||
- crowdsec_data:/var/lib/crowdsec/data
|
||||
- ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||
- traefik_logs:/var/log/traefik:ro
|
||||
networks: [traefik_proxy]
|
||||
|
||||
# Auto-register the bouncer once (uses CROWDSEC_BOUNCER_KEY from .env)
|
||||
crowdsec-init:
|
||||
image: crowdsecurity/crowdsec:latest
|
||||
container_name: crowdsec-init
|
||||
depends_on: [crowdsec]
|
||||
entrypoint: sh -c "cscli bouncers add traefik-bouncer -k ${CROWDSEC_BOUNCER_KEY} || true && sleep 2"
|
||||
networks: [traefik_proxy]
|
||||
restart: "no"
|
||||
|
||||
traefik-bouncer:
|
||||
image: crowdsecurity/traefik-bouncer:latest
|
||||
container_name: traefik-bouncer
|
||||
restart: unless-stopped
|
||||
depends_on: [crowdsec, crowdsec-init]
|
||||
environment:
|
||||
CROWDSEC_BOUNCER_API_KEY: "${CROWDSEC_BOUNCER_KEY}"
|
||||
CROWDSEC_AGENT_HOST: crowdsec:8080
|
||||
GIN_MODE: release
|
||||
networks: [traefik_proxy]
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## Uptime Kuma — status page / checks
|
||||
## ─────────────────────────────────────────────
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma:1
|
||||
container_name: uptime-kuma
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- uptime_kuma_data:/app/data
|
||||
networks: [traefik_proxy]
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.kuma.rule=Host(`status.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.kuma.entrypoints=websecure
|
||||
- traefik.http.routers.kuma.tls.certresolver=le
|
||||
- traefik.http.routers.kuma.middlewares=crowdsec,authelia,security-headers
|
||||
- traefik.http.services.kuma.loadbalancer.server.port=3001
|
||||
|
||||
## ─────────────────────────────────────────────
|
||||
## Prometheus + exporters + Grafana
|
||||
## ─────────────────────────────────────────────
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: prometheus
|
||||
restart: unless-stopped
|
||||
networks: [monitoring, traefik_proxy]
|
||||
volumes:
|
||||
- prometheus_data:/prometheus
|
||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.prom.rule=Host(`prometheus.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.prom.entrypoints=websecure
|
||||
- traefik.http.routers.prom.tls.certresolver=le
|
||||
- traefik.http.routers.prom.middlewares=crowdsec,authelia,security-headers
|
||||
- traefik.http.services.prom.loadbalancer.server.port=9090
|
||||
|
||||
cadvisor:
|
||||
image: gcr.io/cadvisor/cadvisor:latest
|
||||
container_name: cadvisor
|
||||
restart: unless-stopped
|
||||
networks: [monitoring]
|
||||
devices:
|
||||
- /dev/kmsg:/dev/kmsg
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:ro
|
||||
- /sys:/sys:ro
|
||||
- /var/lib/docker/:/var/lib/docker:ro
|
||||
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
container_name: node-exporter
|
||||
restart: unless-stopped
|
||||
networks: [monitoring]
|
||||
pid: host
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command: ["--path.rootfs=/rootfs"]
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana-oss:latest
|
||||
container_name: grafana
|
||||
restart: unless-stopped
|
||||
networks: [traefik_proxy, monitoring]
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_USER: "${GRAFANA_ADMIN_USER}"
|
||||
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_ADMIN_PASS}"
|
||||
GF_SERVER_ROOT_URL: https://grafana.gate.${DOMAIN}
|
||||
TZ: "${TZ}"
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.grafana.rule=Host(`grafana.gate.${DOMAIN}`)
|
||||
- traefik.http.routers.grafana.entrypoints=websecure
|
||||
- traefik.http.routers.grafana.tls.certresolver=le
|
||||
- traefik.http.routers.grafana.middlewares=crowdsec,authelia,security-headers
|
||||
- traefik.http.services.grafana.loadbalancer.server.port=3000
|
||||
Reference in New Issue
Block a user