Files
launchpad-gateway/docker-compose.yml
2025-08-11 01:09:41 +03:00

302 lines
12 KiB
YAML

# 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 (HTTP-01) + CrowdSec plugin
## ─────────────────────────────────────────────
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
networks: [traefik_proxy, monitoring]
environment:
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 HTTP-01 (no registrar API needed)
- --certificatesresolvers.le.acme.email=${ACME_EMAIL}
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
# (Alt) Use TLS-ALPN-01 if port 80 is blocked:
# - --certificatesresolvers.le.acme.tlschallenge=true
# Metrics (Prometheus)
- --metrics.prometheus=true
- --metrics.prometheus.addrouterslabels=true
# Logs
- --accesslog.filepath=/var/log/traefik/access.log
- --accesslog.bufferingsize=100
- --log.level=INFO
# CrowdSec Traefik plugin (recommended vs sidecar)
- --experimental.plugins.crowdsecbouncer.moduleName=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
- --experimental.plugins.crowdsecbouncer.version=v1.4.4
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 plugin middleware (reusable)
- traefik.http.middlewares.crowdsec.plugin.crowdsecbouncer.enabled=true
- traefik.http.middlewares.crowdsec.plugin.crowdsecbouncer.crowdseclapiurl=http://crowdsec:8080/
- traefik.http.middlewares.crowdsec.plugin.crowdsecbouncer.crowdseclapikey=${CROWDSEC_BOUNCER_KEY}
- traefik.http.middlewares.crowdsec.plugin.crowdsecbouncer.crowdsecmode=stream
# 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:
- ./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) — with Traefik plugin
## ─────────────────────────────────────────────
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/config.yaml:/etc/crowdsec/config.yaml:ro
- ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
- traefik_logs:/var/log/traefik:ro
networks: [traefik_proxy]
# Auto-register the API key used by the Traefik plugin
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"
## ─────────────────────────────────────────────
## 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