Remove deprecated deploy workflow, update README with new features and service URLs, delete unused alertmanager and prometheus configurations, refactor Authelia configuration for improved security and functionality, add Gitea and Beszel services, and enhance health checks across services.

This commit is contained in:
elfateh4
2025-12-02 03:16:38 +01:00
parent f818bd3eca
commit 58cc1b1e92
12 changed files with 279 additions and 618 deletions

View File

@@ -1,20 +1,13 @@
# .env.example
# Copy this file to .env and update the values below
## Domain / Timezone
DOMAIN=your-domain.com
TZ=Your/Timezone
DOMAIN_PREFIX=gate
TZ=Africa/Cairo
## ACME (Let's Encrypt)
# Your email address for Let's Encrypt certificate notifications
ACME_EMAIL=admin@your-domain.com
## Basic Authentication
# Generate password hash with: echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g
# Or use online generator: https://hostingcanada.org/htpasswd-generator/
# Format: username:$$2y$$10$$hashed_password
BASIC_AUTH_USERS=admin:$$2y$$05$$HIIpotyLuLRMBX3sfUs6E.YM3lP9cpF7hK7sHaSXGs6mw/RS6MXCa
## Umami (PostgreSQL)
# Database user for Umami analytics
UMAMI_DB_USER=umami
@@ -24,8 +17,6 @@ UMAMI_DB_PASS=your_strong_database_password
UMAMI_DB_NAME=umami
# Random 64-character secret for Umami app security
UMAMI_APP_SECRET=your_64_character_random_secret_here
# Website ID from Umami dashboard (use placeholder initially, update after setup)
UMAMI_WEBSITE_ID=placeholder-website-id
## pgAdmin
# Default email for pgAdmin login
@@ -33,8 +24,26 @@ PGADMIN_DEFAULT_EMAIL=admin@your-domain.com
# Strong password for pgAdmin login
PGADMIN_DEFAULT_PASSWORD=your_strong_pgadmin_password
## Grafana
# Admin username for Grafana dashboard
GRAFANA_ADMIN_USER=admin
# Strong password for Grafana admin user
GRAFANA_ADMIN_PASS=your_strong_grafana_password
## Authelia
# Database password for Authelia PostgreSQL
AUTHELIA_DB_PASSWORD=your_authelia_db_password
# JWT secret for Authelia (64+ character random string)
AUTHELIA_JWT_SECRET=your_64_character_jwt_secret_here
# Session secret for Authelia (64+ character random string)
AUTHELIA_SESSION_SECRET=your_64_character_session_secret_here
# Storage encryption key for Authelia (20+ character string)
AUTHELIA_STORAGE_ENCRYPTION_KEY=your_storage_encryption_key_here
# SMTP password for Authelia notifier
AUTHELIA_NOTIFIER_SMTP_PASSWORD=your_smtp_password_here
## Gitea
# Database user for Gitea
GITEA_DB_USER=gitea
# Database password for Gitea PostgreSQL
GITEA_DB_PASSWORD=your_gitea_db_password_here
# Database name for Gitea
GITEA_DB_NAME=gitea
# Secret key for Gitea (64+ character random string - generate with: openssl rand -hex 32)
GITEA_SECRET_KEY=your_64_character_gitea_secret_key_here
# Internal token for Gitea (generate with: docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret INTERNAL_TOKEN)
GITEA_INTERNAL_TOKEN=your_gitea_internal_token_here

View File

@@ -1,60 +0,0 @@
# .github/workflows/deploy.yml
name: Deploy Gateway to VPS
on:
push:
branches: [ "disbaled" ]
paths:
- "docker-compose.yml"
- ".github/workflows/deploy.yml"
workflow_dispatch:
env:
REMOTE_DIR: ${{ secrets.REMOTE_DIR }}
concurrency:
group: deploy-prod
cancel-in-progress: true
jobs:
deploy:
name: Ship to VPS
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts
- name: Login to GitHub Container Registry on VPS
run: |
ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \
"echo '${{ secrets.GITHUB_TOKEN }}' | docker login ghcr.io -u '${{ github.actor }}' --password-stdin"
- name: Deploy (pull, up, prune)
run: |
ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \
"export REMOTE_DIR='${{ secrets.REMOTE_DIR }}'; bash -se" <<'EOF'
set -euo pipefail
cd "$REMOTE_DIR"
git pull origin main
docker compose pull
docker compose up -d --remove-orphans
docker image prune -af || true
EOF
- name: Post-deploy smoke checks
run: |
echo "Deployed to ${{ secrets.SSH_HOST }}:${{ secrets.SSH_PORT }} → ${REMOTE_DIR}"
echo "Traefik: https://traefik.gate.${{ secrets.DOMAIN }}"
echo "Portainer: https://portainer.gate.${{ secrets.DOMAIN }}"
echo "Uptime Kuma: https://uptime.gate.${{ secrets.DOMAIN }}"
echo "Umami: https://umami.gate.${{ secrets.DOMAIN }}"
echo "pgAdmin: https://pgadmin.gate.${{ secrets.DOMAIN }}"

View File

@@ -1,28 +1,35 @@
# Launchpad Gateway
A production-ready Traefik-based reverse proxy gateway with automatic SSL/TLS, analytics, monitoring, and container management.
A production-ready Traefik-based reverse proxy gateway with automatic SSL/TLS, analytics, monitoring, authentication, and container management.
## 🚀 Features
- **Automatic SSL/TLS** certificates via Let's Encrypt
- **Reverse Proxy** with Traefik v3.1
- **Authentication** with Authelia (2FA, password reset)
- **Web Analytics** with Umami and PostgreSQL
- **Container Management** via Portainer
- **Uptime Monitoring** with Uptime Kuma
- **Security Headers** and Basic Authentication
- **Server Monitoring** with Beszel
- **Git Repository Hosting** with Gitea
- **Security Headers** and Flexible Routing
- **Flexible Domain Routing** (subdomains, paths, custom rules)
## 🏗️ Architecture
```
Internet → Traefik (Port 80/443) → Internal Services (traefik_proxy network)
Internet → Traefik (Port 80/443) → Authelia (Auth) → Internal Services (traefik_proxy network)
```
### Current Services
- **Traefik Dashboard**: `traefik.gate.${DOMAIN}` - Reverse proxy management
- **Portainer**: `portainer.gate.${DOMAIN}` - Docker container management
- **Uptime Kuma**: `uptime.gate.${DOMAIN}` - Service monitoring
- **Umami Analytics**: `umami.gate.${DOMAIN}` - Web analytics dashboard
- **Traefik Dashboard**: `traefik.${DOMAIN_PREFIX}.${DOMAIN}` - Reverse proxy management
- **Authelia**: `auth.${DOMAIN_PREFIX}.${DOMAIN}` - Authentication portal
- **Portainer**: `portainer.${DOMAIN_PREFIX}.${DOMAIN}` - Docker container management
- **Uptime Kuma**: `uptime.${DOMAIN_PREFIX}.${DOMAIN}` - Service monitoring
- **Umami Analytics**: `umami.${DOMAIN_PREFIX}.${DOMAIN}` - Web analytics dashboard
- **pgAdmin**: `pgadmin.${DOMAIN_PREFIX}.${DOMAIN}` - PostgreSQL administration
- **Beszel**: `beszel.${DOMAIN_PREFIX}.${DOMAIN}` - Server monitoring
- **Gitea**: `git.${DOMAIN_PREFIX}.${DOMAIN}` - Self-hosted Git service (SSH on port 222)
## 🛠️ Quick Start
@@ -49,19 +56,31 @@ Update `.env` with your settings:
```bash
# Domain and timezone
DOMAIN=your-domain.com
TZ=Your/Timezone
DOMAIN_PREFIX=test
TZ=Africa/Cairo
# Let's Encrypt email
ACME_EMAIL=admin@your-domain.com
# Basic auth (generate with: htpasswd -nB admin)
BASIC_AUTH_USERS=admin:$$2y$$05$$your_hashed_password
# Database credentials
UMAMI_DB_USER=umami
UMAMI_DB_PASS=your_secure_password
UMAMI_DB_NAME=umami
UMAMI_APP_SECRET=your_64_character_secret
# Authelia secrets (generate with openssl rand -hex 32)
AUTHELIA_DB_PASSWORD=your_secure_password
AUTHELIA_JWT_SECRET=your_64_char_secret
AUTHELIA_SESSION_SECRET=your_64_char_secret
AUTHELIA_STORAGE_ENCRYPTION_KEY=your_64_char_secret
AUTHELIA_NOTIFIER_SMTP_PASSWORD=your_smtp_password
# Gitea secrets
GITEA_DB_USER=gitea
GITEA_DB_PASSWORD=your_secure_password
GITEA_DB_NAME=gitea
GITEA_SECRET_KEY=your_64_char_secret
GITEA_INTERNAL_TOKEN=your_internal_token
```
@@ -71,10 +90,14 @@ docker compose up -d
```
### 4. Access Services
- **Traefik Dashboard**: `https://traefik.gate.your-domain.com`
- **Portainer**: `https://portainer.gate.your-domain.com`
- **Uptime Kuma**: `https://uptime.gate.your-domain.com`
- **Umami Analytics**: `https://umami.gate.your-domain.com`
- **Traefik Dashboard**: `https://traefik.${DOMAIN_PREFIX}.${DOMAIN}`
- **Authelia**: `https://auth.${DOMAIN_PREFIX}.${DOMAIN}`
- **Portainer**: `https://portainer.${DOMAIN_PREFIX}.${DOMAIN}`
- **Uptime Kuma**: `https://uptime.${DOMAIN_PREFIX}.${DOMAIN}`
- **Umami Analytics**: `https://umami.${DOMAIN_PREFIX}.${DOMAIN}`
- **pgAdmin**: `https://pgadmin.${DOMAIN_PREFIX}.${DOMAIN}`
- **Beszel**: `https://beszel.${DOMAIN_PREFIX}.${DOMAIN}`
- **Gitea**: `https://git.${DOMAIN_PREFIX}.${DOMAIN}` (SSH: port 222)
## 📋 Adding New Services
@@ -231,16 +254,15 @@ services:
### Available Middlewares
- **`security-headers`**: HSTS, XSS protection, content type sniffing prevention
- **`basic-auth`**: HTTP Basic Authentication for admin interfaces
- **`umami-analytics`**: Automatic web analytics tracking
- **`authelia`**: Authentication and authorization with 2FA support
### Usage Examples
```yaml
# Public application with analytics
- traefik.http.routers.app.middlewares=umami-analytics,security-headers
- traefik.http.routers.app.middlewares=security-headers
# Admin interface with authentication
- traefik.http.routers.admin.middlewares=basic-auth,security-headers
- traefik.http.routers.admin.middlewares=authelia@docker,security-headers
# API endpoint (security headers only)
- traefik.http.routers.api.middlewares=security-headers
@@ -274,6 +296,7 @@ services:
### Built-in Monitoring
- **Uptime Kuma**: Service availability monitoring
- **Beszel**: Server resource monitoring (CPU, RAM, disk, network)
- **Traefik Dashboard**: Traffic and routing metrics
- **Umami Analytics**: Web traffic analytics (privacy-focused)

View File

@@ -1,17 +0,0 @@
global:
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alertmanager@example.com'
smtp_auth_username: 'alertmanager'
smtp_auth_password: 'dummy_password'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: 'admin@example.com'

View File

@@ -0,0 +1,60 @@
---
# Authelia configuration
# This is a minimal configuration for getting started with Authelia
server:
address: 'tcp://:9091'
endpoints:
authz:
forward-auth:
implementation: 'ForwardAuth'
authentication_backend:
file:
path: '/config/users_database.yml'
access_control:
default_policy: 'one_factor'
rules:
- domain: 'pgadmin.${DOMAIN_PREFIX}.${DOMAIN}'
policy: 'two_factor'
- domain: 'beszel.${DOMAIN_PREFIX}.${DOMAIN}'
policy: 'two_factor'
- domain: 'traefik.${DOMAIN_PREFIX}.${DOMAIN}'
policy: 'two_factor'
- domain: '*.${DOMAIN_PREFIX}.${DOMAIN}'
policy: 'one_factor'
session:
name: 'authelia_session'
cookies:
- domain: '${DOMAIN}'
authelia_url: 'https://auth.${DOMAIN_PREFIX}.${DOMAIN}'
default_redirection_url: 'https://portainer.${DOMAIN_PREFIX}.${DOMAIN}'
storage:
postgres:
address: 'tcp://authelia-db:5432'
database: 'authelia'
username: 'authelia'
notifier:
disable_startup_check: true
# Configure SMTP for production email notifications
# For testing, you can use filesystem notifier instead:
# filesystem:
# filename: /config/notification.txt
smtp:
address: 'submissions://smtp.gmail.com:465'
username: 'your-email@gmail.com'
sender: 'Authelia <noreply@${DOMAIN}>'
# For Gmail, use an App Password (not your regular password)
# Generate at: https://myaccount.google.com/apppasswords
identity_validation:
reset_password: {}
regulation:
max_retries: 3
find_time: 120
ban_time: 300

View File

@@ -3,7 +3,7 @@
# This is a minimal configuration for getting started with Authelia
server:
port: 9091
address: 'tcp://:9091'
endpoints:
authz:
forward-auth:
@@ -14,45 +14,37 @@ authentication_backend:
path: '/config/users_database.yml'
access_control:
default_policy: 'deny'
default_policy: 'one_factor'
rules:
- domain: 'pgadmin.gate.${DOMAIN}'
- domain: 'pgadmin.test.3launchpad.com'
policy: 'two_factor'
- domain: '*.gate.${DOMAIN}'
- domain: '*.test.3launchpad.com'
policy: 'one_factor'
session:
name: 'authelia_session'
secret: '${AUTHELIA_SESSION_SECRET}'
cookies:
- domain: 'gate.${DOMAIN}'
authelia_url: 'https://auth.gate.${DOMAIN}'
default_redirection_url: 'https://login.gate.${DOMAIN}'
- domain: '3launchpad.com'
authelia_url: 'https://auth.test.3launchpad.com'
default_redirection_url: 'https://portainer.test.3launchpad.com'
storage:
postgres:
host: 'authelia-db'
port: 5432
address: 'tcp://authelia-db:5432'
database: 'authelia'
username: 'authelia'
password: '${AUTHELIA_DB_PASSWORD}'
notifier:
disable_startup_check: true
smtp:
address: 'smtp://localhost:25'
username: 'authelia'
password: 'dummy_password'
host: 'smtp.example.com'
port: 587
sender: 'authelia@example.com'
jwt_secret: '${AUTHELIA_JWT_SECRET}'
identity_validation:
reset_password: {}
api:
endpoints:
reset_password:
disable: false
regulations:
regulation:
max_retries: 3
find_time: 120
ban_time: 300
ban_time: 300

View File

@@ -1,10 +1,17 @@
---
# Authelia users database
# This is a simple file-based user database for testing
# This is a simple file-based user database
#
# To generate a new password hash, run:
# docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD'
#
# IMPORTANT: Change the default password before production use!
users:
admin:
displayname: "Administrator"
password: "$argon2id$v=19$m=65536,t=3,p=4$abcdefghijklmnopqrstuvwx$abcdefghijklmnopqrstuvwxabcdefghijklmnopqrstuvwx" # Dummy hash for 'password'
email: admin@example.com
groups: []
# Default password: "Admin@123456" - CHANGE THIS!
password: "$argon2id$v=19$m=65536,t=3,p=4$O1Qjq7AB4/xJ7Qk1dUqp/g$PhVqFWEqyQTJeSnCeiCC3lrcWcpw37kYttw4Xh/qUsk"
email: admin@3launchpad.com
groups:
- admins

View File

@@ -0,0 +1 @@
c17e1b1086d55cc7d4bfacbfa6b9472ca975863413fb629f

View File

@@ -1,337 +0,0 @@
########################
# Networks & Volumes
########################
networks:
traefik_proxy:
name: traefik_proxy
volumes:
traefik_letsencrypt:
traefik_logs:
portainer_data:
uptime_kuma_data:
umami_data:
pgadmin_data:
authelia_config:
authelia_db_data:
grafana_data:
prometheus_data:
alertmanager_data:
########################
# Services
########################
services:
## ─────────────────────────────────────────────
## Traefik — edge router + ACME (HTTP-01)
## ─────────────────────────────────────────────
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
# Mail protocol ports for MailCow integration
- "25:25" # SMTP
- "465:465" # SMTPS
- "587:587" # Submission
- "143:143" # IMAP
- "993:993" # IMAPS
- "110:110" # POP3
- "995:995" # POP3S
- "4190:4190" # ManageSieve
networks: [traefik_proxy]
environment:
TZ: "${TZ}"
command:
# Experimental plugins
- --experimental.plugins.traefik-umami-plugin.modulename=github.com/1cedsoda/traefik-umami-plugin
- --experimental.plugins.traefik-umami-plugin.version=v1.0.3
# 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
- --entrypoints.web.forwardedheaders.insecure=true
- --entrypoints.websecure.forwardedheaders.insecure=true
# Mail protocol entrypoints for MailCow integration
- --entrypoints.smtp.address=:25
- --entrypoints.smtps.address=:465
- --entrypoints.submission.address=:587
- --entrypoints.imap.address=:143
- --entrypoints.imaps.address=:993
- --entrypoints.pop3.address=:110
- --entrypoints.pop3s.address=:995
- --entrypoints.sieve.address=:4190
# Dashboard/API (internal)
- --api=true
- --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
# Global timeouts for slow backends
- --serversTransport.forwardingTimeouts.dialTimeout=30s
- --serversTransport.forwardingTimeouts.responseHeaderTimeout=60s
- --serversTransport.forwardingTimeouts.idleConnTimeout=180s
# Logs
- --accesslog.filepath=/var/log/traefik/access.log
- --accesslog.bufferingsize=100
- --log.level=INFO
- --metrics.prometheus=true
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
# # Basic Auth middleware
# - traefik.http.middlewares.basic-auth.basicauth.users=${BASIC_AUTH_USERS}
# Authelia middleware
- traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/authz/forward-auth
- traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true
- traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Name
# Traefik dashboard (protected)
- traefik.http.routers.traefik.rule=Host(`traefik.test.${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=authelia@docker,security-headers
## ─────────────────────────────────────────────
## Authelia — authentication and authorization
## ─────────────────────────────────────────────
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- authelia_config:/config
environment:
TZ: "${TZ}"
AUTHELIA_DB_PASSWORD: "${AUTHELIA_DB_PASSWORD}"
AUTHELIA_JWT_SECRET: "${AUTHELIA_JWT_SECRET}"
AUTHELIA_SESSION_SECRET: "${AUTHELIA_SESSION_SECRET}"
depends_on:
- authelia-db
labels:
- traefik.enable=true
- traefik.http.routers.authelia.rule=Host(`auth.test.${DOMAIN}`)
- traefik.http.routers.authelia.entrypoints=websecure
- traefik.http.routers.authelia.tls.certresolver=le
- traefik.http.services.authelia.loadbalancer.server.port=9091
## ─────────────────────────────────────────────
## Authelia Database — PostgreSQL
## ─────────────────────────────────────────────
authelia-db:
image: postgres:15-alpine
container_name: authelia-db
restart: unless-stopped
networks: [traefik_proxy]
environment:
POSTGRES_DB: authelia
POSTGRES_USER: authelia
POSTGRES_PASSWORD: ${AUTHELIA_DB_PASSWORD}
volumes:
- authelia_db_data:/var/lib/postgresql/data
## ─────────────────────────────────────────────
## 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.test.${DOMAIN}`)
- traefik.http.routers.portainer.entrypoints=websecure
- traefik.http.routers.portainer.tls.certresolver=le
- traefik.http.routers.portainer.middlewares=security-headers
- traefik.http.services.portainer.loadbalancer.server.port=9000
## ─────────────────────────────────────────────
## Uptime Kuma — status page / checks
## ─────────────────────────────────────────────
uptime-kuma:
image: louislam/uptime-kuma:latest
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(`uptime.test.${DOMAIN}`)
- traefik.http.routers.kuma.entrypoints=websecure
- traefik.http.routers.kuma.tls.certresolver=le
- traefik.http.routers.kuma.middlewares=security-headers
- traefik.http.services.kuma.loadbalancer.server.port=3001
## ─────────────────────────────────────────────
## Umami — web analytics
## ─────────────────────────────────────────────
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
restart: unless-stopped
networks: [traefik_proxy]
environment:
DATABASE_URL: postgresql://${UMAMI_DB_USER}:${UMAMI_DB_PASS}@umami-db:5432/${UMAMI_DB_NAME}
DATABASE_TYPE: postgresql
APP_SECRET: ${UMAMI_APP_SECRET}
depends_on:
- umami-db
labels:
- traefik.enable=true
- traefik.http.routers.umami.rule=Host(`umami.test.${DOMAIN}`)
- traefik.http.routers.umami.entrypoints=websecure
- traefik.http.routers.umami.tls.certresolver=le
- traefik.http.routers.umami.middlewares=security-headers
- traefik.http.services.umami.loadbalancer.server.port=3000
## ─────────────────────────────────────────────
## Umami Database — PostgreSQL
## ─────────────────────────────────────────────
umami-db:
image: postgres:15-alpine
container_name: umami-db
restart: unless-stopped
networks: [traefik_proxy]
environment:
POSTGRES_DB: ${UMAMI_DB_NAME}
POSTGRES_USER: ${UMAMI_DB_USER}
POSTGRES_PASSWORD: ${UMAMI_DB_PASS}
volumes:
- umami_data:/var/lib/postgresql/data
# ─────────────────────────────────────────────
# pgAdmin — PostgreSQL administration
# ─────────────────────────────────────────────
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: unless-stopped
networks: [traefik_proxy]
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
PGADMIN_CONFIG_SERVER_MODE: 'True'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
# Fix CSRF issues behind reverse proxy
PGADMIN_CONFIG_WTF_CSRF_CHECK_DEFAULT: 'False'
PGADMIN_CONFIG_WTF_CSRF_TIME_LIMIT: 'None'
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'False'
# Trust proxy headers
PGADMIN_CONFIG_PROXY_X_HOST_COUNT: '1'
PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT: '1'
volumes:
- pgadmin_data:/var/lib/pgadmin
labels:
- traefik.enable=true
- traefik.http.routers.pgadmin.rule=Host(`pgadmin.test.${DOMAIN}`)
- traefik.http.routers.pgadmin.entrypoints=websecure
- traefik.http.routers.pgadmin.tls.certresolver=le
- traefik.http.services.pgadmin.loadbalancer.server.port=80
## ─────────────────────────────────────────────
## Prometheus — monitoring
## ─────────────────────────────────────────────
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./rules.yml:/etc/prometheus/rules.yml
- prometheus_data:/prometheus
- /var/run/docker.sock:/var/run/docker.sock:ro
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
labels:
- traefik.enable=true
- traefik.http.routers.prometheus.rule=Host(`prometheus.test.${DOMAIN}`)
- traefik.http.routers.prometheus.entrypoints=websecure
- traefik.http.routers.prometheus.tls.certresolver=le
- traefik.http.routers.prometheus.middlewares=authelia@docker,security-headers
- traefik.http.services.prometheus.loadbalancer.server.port=9090
## ─────────────────────────────────────────────
## Grafana — visualization
## ─────────────────────────────────────────────
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
networks: [traefik_proxy]
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
labels:
- traefik.enable=true
- traefik.http.routers.grafana.rule=Host(`grafana.test.${DOMAIN}`)
- traefik.http.routers.grafana.entrypoints=websecure
- traefik.http.routers.grafana.tls.certresolver=le
- traefik.http.routers.grafana.middlewares=authelia@docker,security-headers
- traefik.http.services.grafana.loadbalancer.server.port=3000
## ─────────────────────────────────────────────
## Alertmanager — alert handling
## ─────────────────────────────────────────────
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
labels:
- traefik.enable=true
- traefik.http.routers.alertmanager.rule=Host(`alertmanager.test.${DOMAIN}`)
- traefik.http.routers.alertmanager.entrypoints=websecure
- traefik.http.routers.alertmanager.tls.certresolver=le
- traefik.http.routers.alertmanager.middlewares=authelia@docker,security-headers
- traefik.http.services.alertmanager.loadbalancer.server.port=9093

View File

@@ -12,11 +12,10 @@ volumes:
uptime_kuma_data:
umami_data:
pgadmin_data:
authelia_config:
authelia_db_data:
grafana_data:
prometheus_data:
alertmanager_data:
beszel_data:
gitea_data:
gitea_db_data:
########################
# Services
@@ -46,6 +45,9 @@ services:
environment:
TZ: "${TZ}"
command:
# Enable ping endpoint for health checks
- --ping=true
# Experimental plugins
- --experimental.plugins.traefik-umami-plugin.modulename=github.com/1cedsoda/traefik-umami-plugin
- --experimental.plugins.traefik-umami-plugin.version=v1.0.3
@@ -118,11 +120,17 @@ services:
- traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Name
# Traefik dashboard (protected)
- traefik.http.routers.traefik.rule=Host(`traefik.gate.${DOMAIN}`)
- traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN_PREFIX}.${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=authelia@docker,security-headers
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Authelia — authentication and authorization
@@ -133,17 +141,27 @@ services:
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- authelia_config:/config
- ./authelia:/config
entrypoint: /bin/sh
command:
- -c
- |
eval "echo \"$(cat /config/configuration.template.yml)\"" > /config/configuration.yml
exec /app/entrypoint.sh
environment:
TZ: "${TZ}"
AUTHELIA_DB_PASSWORD: "${AUTHELIA_DB_PASSWORD}"
AUTHELIA_JWT_SECRET: "${AUTHELIA_JWT_SECRET}"
AUTHELIA_SESSION_SECRET: "${AUTHELIA_SESSION_SECRET}"
DOMAIN: "${DOMAIN}"
DOMAIN_PREFIX: "${DOMAIN_PREFIX}"
AUTHELIA_SESSION_SECRET: '${AUTHELIA_SESSION_SECRET}'
AUTHELIA_STORAGE_ENCRYPTION_KEY: '${AUTHELIA_STORAGE_ENCRYPTION_KEY}'
AUTHELIA_STORAGE_POSTGRES_PASSWORD: '${AUTHELIA_DB_PASSWORD}'
AUTHELIA_NOTIFIER_SMTP_PASSWORD: '${AUTHELIA_NOTIFIER_SMTP_PASSWORD}'
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: '${AUTHELIA_JWT_SECRET}'
depends_on:
- authelia-db
labels:
- traefik.enable=true
- traefik.http.routers.authelia.rule=Host(`auth.gate.${DOMAIN}`)
- traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.authelia.entrypoints=websecure
- traefik.http.routers.authelia.tls.certresolver=le
- traefik.http.services.authelia.loadbalancer.server.port=9091
@@ -162,6 +180,12 @@ services:
POSTGRES_PASSWORD: ${AUTHELIA_DB_PASSWORD}
volumes:
- authelia_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U authelia -d authelia"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
## ─────────────────────────────────────────────
## Portainer — Docker control plane
@@ -171,12 +195,13 @@ services:
container_name: portainer
restart: unless-stopped
networks: [traefik_proxy]
command: --host unix:///var/run/docker.sock
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.rule=Host(`portainer.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.portainer.entrypoints=websecure
- traefik.http.routers.portainer.tls.certresolver=le
- traefik.http.routers.portainer.middlewares=security-headers
@@ -194,7 +219,7 @@ services:
networks: [traefik_proxy]
labels:
- traefik.enable=true
- traefik.http.routers.kuma.rule=Host(`uptime.gate.${DOMAIN}`)
- traefik.http.routers.kuma.rule=Host(`uptime.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.kuma.entrypoints=websecure
- traefik.http.routers.kuma.tls.certresolver=le
- traefik.http.routers.kuma.middlewares=security-headers
@@ -216,11 +241,17 @@ services:
- umami-db
labels:
- traefik.enable=true
- traefik.http.routers.umami.rule=Host(`umami.gate.${DOMAIN}`)
- traefik.http.routers.umami.rule=Host(`umami.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.umami.entrypoints=websecure
- traefik.http.routers.umami.tls.certresolver=le
- traefik.http.routers.umami.middlewares=security-headers
- traefik.http.services.umami.loadbalancer.server.port=3000
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Umami Database — PostgreSQL
@@ -236,6 +267,12 @@ services:
POSTGRES_PASSWORD: ${UMAMI_DB_PASS}
volumes:
- umami_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${UMAMI_DB_USER} -d ${UMAMI_DB_NAME}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ─────────────────────────────────────────────
# pgAdmin — PostgreSQL administration
@@ -250,88 +287,113 @@ services:
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
PGADMIN_CONFIG_SERVER_MODE: 'True'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
# Fix CSRF issues behind reverse proxy
PGADMIN_CONFIG_WTF_CSRF_CHECK_DEFAULT: 'False'
PGADMIN_CONFIG_WTF_CSRF_TIME_LIMIT: 'None'
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'False'
# Trust proxy headers
PGADMIN_CONFIG_PROXY_X_HOST_COUNT: '1'
PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT: '1'
volumes:
- pgadmin_data:/var/lib/pgadmin
labels:
- traefik.enable=true
- traefik.http.routers.pgadmin.rule=Host(`pgadmin.gate.${DOMAIN}`)
- traefik.http.routers.pgadmin.rule=Host(`pgadmin.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.pgadmin.entrypoints=websecure
- traefik.http.routers.pgadmin.tls.certresolver=le
- traefik.http.routers.pgadmin.middlewares=security-headers
- traefik.http.services.pgadmin.loadbalancer.server.port=80
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Prometheus — monitoring
## Beszel Hub — lightweight server monitoring
## ─────────────────────────────────────────────
prometheus:
image: prom/prometheus:latest
container_name: prometheus
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./rules.yml:/etc/prometheus/rules.yml
- prometheus_data:/prometheus
- /var/run/docker.sock:/var/run/docker.sock:ro
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
- beszel_data:/beszel_data
labels:
- traefik.enable=true
- traefik.http.routers.prometheus.rule=Host(`prometheus.gate.${DOMAIN}`)
- traefik.http.routers.prometheus.entrypoints=websecure
- traefik.http.routers.prometheus.tls.certresolver=le
- traefik.http.routers.prometheus.middlewares=authelia@docker,security-headers
- traefik.http.services.prometheus.loadbalancer.server.port=9090
- traefik.http.routers.beszel.rule=Host(`beszel.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.beszel.entrypoints=websecure
- traefik.http.routers.beszel.tls.certresolver=le
- traefik.http.routers.beszel.middlewares=security-headers
- traefik.http.services.beszel.loadbalancer.server.port=8090
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8090"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Grafana — visualization
## Gitea — self-hosted Git service
## ─────────────────────────────────────────────
grafana:
image: grafana/grafana:latest
container_name: grafana
gitea:
image: docker.gitea.com/gitea:latest
container_name: gitea
restart: unless-stopped
networks: [traefik_proxy]
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-db:5432
- GITEA__database__NAME=${GITEA_DB_NAME}
- GITEA__database__USER=${GITEA_DB_USER}
- GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
- GITEA__server__DOMAIN=git.${DOMAIN_PREFIX}.${DOMAIN}
- GITEA__server__SSH_DOMAIN=git.${DOMAIN_PREFIX}.${DOMAIN}
- GITEA__server__ROOT_URL=https://git.${DOMAIN_PREFIX}.${DOMAIN}/
- GITEA__server__SSH_PORT=222
- GITEA__server__SSH_LISTEN_PORT=22
- GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY}
- GITEA__security__INTERNAL_TOKEN=${GITEA_INTERNAL_TOKEN}
volumes:
- grafana_data:/var/lib/grafana
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "222:22"
depends_on:
- gitea-db
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=authelia@docker,security-headers
- traefik.http.services.grafana.loadbalancer.server.port=3000
- traefik.http.routers.gitea.rule=Host(`git.${DOMAIN_PREFIX}.${DOMAIN}`)
- traefik.http.routers.gitea.entrypoints=websecure
- traefik.http.routers.gitea.tls.certresolver=le
- traefik.http.routers.gitea.middlewares=security-headers
- traefik.http.services.gitea.loadbalancer.server.port=3000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Alertmanager — alert handling
## Gitea Database — PostgreSQL
## ─────────────────────────────────────────────
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
gitea-db:
image: postgres:15-alpine
container_name: gitea-db
restart: unless-stopped
networks: [traefik_proxy]
environment:
POSTGRES_DB: ${GITEA_DB_NAME}
POSTGRES_USER: ${GITEA_DB_USER}
POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD}
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
labels:
- traefik.enable=true
- traefik.http.routers.alertmanager.rule=Host(`alertmanager.gate.${DOMAIN}`)
- traefik.http.routers.alertmanager.entrypoints=websecure
- traefik.http.routers.alertmanager.tls.certresolver=le
- traefik.http.routers.alertmanager.middlewares=authelia@docker,security-headers
- traefik.http.services.alertmanager.loadbalancer.server.port=9093
- gitea_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${GITEA_DB_USER} -d ${GITEA_DB_NAME}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s

View File

@@ -1,43 +0,0 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- rules.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'traefik'
static_configs:
- targets: ['traefik:8080']
metrics_path: /metrics
- job_name: 'authelia'
static_configs:
- targets: ['authelia:9091']
- job_name: 'grafana'
static_configs:
- targets: ['grafana:3000']
- job_name: 'docker'
docker_sd_configs:
- host: unix:///var/run/docker.sock
relabel_configs:
- source_labels: [__meta_docker_container_name]
regex: '/(.*)'
target_label: container_name
- source_labels: [__meta_docker_container_label_com_docker_compose_service]
target_label: service
- action: keep
source_labels: [__meta_docker_container_label_com_docker_compose_service]
regex: '.*'

View File

@@ -1,36 +0,0 @@
groups:
- name: recording_rules
rules:
- record: job:up:sum
expr: sum(up) by (job)
- record: job:up:count
expr: count(up) by (job)
- name: alerting_rules
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes."
- alert: TraefikDown
expr: up{job="traefik"} == 0
for: 2m
labels:
severity: warning
annotations:
summary: "Traefik is down"
description: "Traefik has been down for more than 2 minutes."
- alert: AutheliaDown
expr: up{job="authelia"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Authelia is down"
description: "Authelia authentication service is unavailable."