Compare commits

...

10 Commits

Author SHA1 Message Date
elfateh4
6f953a090d Add Duplicati backup service and update Authelia configuration
- Add Duplicati encrypted cloud backup service
- Add AUTHELIA_DB_NAME and AUTHELIA_DB_USER environment variables
- Add DUPLICATI_ENCRYPTION_KEY and DUPLICATI_PASSWORD to .env.example
- Update README.md with Duplicati documentation
- Configure Gitea with English-only language setting
2025-12-02 05:54:59 +01:00
elfateh4
96e3c80ffb Refactor Authelia database configuration to use environment variables for improved security and flexibility; add Duplicati service for encrypted cloud backup. 2025-12-02 05:48:54 +01:00
elfateh4
9ac7fefb3d Update Gitea service to include Authelia middleware for enhanced security 2025-12-02 04:05:54 +01:00
elfateh4
a5cd34d9ab Update Authelia access control rules and SMTP configuration; modify Traefik labels for consistency 2025-12-02 04:04:56 +01:00
elfateh4
58cc1b1e92 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. 2025-12-02 03:16:38 +01:00
Mohmmed Elfateh Sabry
f818bd3eca Add docker-compose configuration for Traefik, Authelia, and associated services 2025-12-02 00:12:32 +02:00
elfateh4
a924adee27 Add Prometheus and Grafana services with alerting configuration 2025-12-01 19:18:08 +01:00
elfateh4
47e640b969 Update deploy workflow branch and add Authelia configuration and user database 2025-12-01 19:10:10 +01:00
root
4b23c27bc1 Update Traefik image tag to latest in docker-compose.yml 2025-10-30 02:40:23 +00:00
root
166641be57 Fix Uptime Kuma image tag case and update Traefik middleware configuration 2025-10-30 02:34:12 +00:00
8 changed files with 482 additions and 151 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,36 @@ 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 name for Authelia
AUTHELIA_DB_NAME=authelia
# Database user for Authelia
AUTHELIA_DB_USER=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 (64+ character random 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
## Duplicati
# Settings encryption key for Duplicati (random string for encrypting settings database)
DUPLICATI_ENCRYPTION_KEY=your_duplicati_encryption_key_here
# Web UI password for Duplicati
DUPLICATI_PASSWORD=your_duplicati_password_here

View File

@@ -1,60 +0,0 @@
# .github/workflows/deploy.yml
name: Deploy Gateway to VPS
on:
push:
branches: [ "main" ]
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,37 @@
# 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
- **Encrypted Backups** with Duplicati
- **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)
- **Duplicati**: `backup.${DOMAIN_PREFIX}.${DOMAIN}` - Encrypted cloud backup
## 🛠️ Quick Start
@@ -49,19 +58,37 @@ 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_NAME=authelia
AUTHELIA_DB_USER=authelia
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
# Duplicati secrets
DUPLICATI_ENCRYPTION_KEY=your_encryption_key
DUPLICATI_PASSWORD=your_backup_password
```
@@ -71,10 +98,15 @@ 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)
- **Duplicati**: `https://backup.${DOMAIN_PREFIX}.${DOMAIN}`
## 📋 Adding New Services
@@ -231,16 +263,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 +305,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

@@ -0,0 +1,61 @@
---
# 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_DB_NAME}'
username: '${AUTHELIA_DB_USER}'
password: '${AUTHELIA_DB_PASSWORD}'
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

@@ -0,0 +1,61 @@
---
# 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.test.3launchpad.com'
policy: 'two_factor'
- domain: 'beszel.test.3launchpad.com'
policy: 'two_factor'
- domain: 'traefik.test.3launchpad.com'
policy: 'two_factor'
- domain: '*.test.3launchpad.com'
policy: 'one_factor'
session:
name: 'authelia_session'
cookies:
- domain: '3launchpad.com'
authelia_url: 'https://auth.test.3launchpad.com'
default_redirection_url: 'https://portainer.test.3launchpad.com'
storage:
postgres:
address: 'tcp://authelia-db:5432'
database: 'authelia'
username: 'authelia'
password: 'p/6EGIgTxPndniwUCY54G7q5jOPqXofF'
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@3launchpad.com>'
# 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

@@ -0,0 +1,17 @@
---
# Authelia users database
# 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"
# 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

@@ -12,6 +12,11 @@ volumes:
uptime_kuma_data:
umami_data:
pgadmin_data:
authelia_db_data:
beszel_data:
gitea_data:
gitea_db_data:
duplicati_config:
########################
# Services
@@ -22,7 +27,7 @@ services:
## Traefik — edge router + ACME (HTTP-01)
## ─────────────────────────────────────────────
traefik:
image: traefik:v3.1
image: traefik:latest
container_name: traefik
restart: unless-stopped
ports:
@@ -41,6 +46,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
@@ -88,42 +96,98 @@ services:
- --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
- /var/run/docker.sock:/var/rundocker.sock:ro
- traefik_letsencrypt:/letsencrypt
- traefik_logs:/var/log/traefik
labels:
- traefik.enable=true
- "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
# Reusable security headers middleware
- "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}
# Umami Analytics middleware (commented out until real website ID is available)
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.umamiHost=umami:3000
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.websiteId=${UMAMI_WEBSITE_ID}
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.forwardPath=umami
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.scriptInjection=true
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.scriptInjectionMode=tag
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.autoTrack=true
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.doNotTrack=false
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.cache=false
# - traefik.http.middlewares.umami-analytics.plugin.traefik-umami-plugin.serverSideTracking=false
# 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.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=basic-auth,security-headers
- "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@docker"
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
## ─────────────────────────────────────────────
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- ./authelia:/config
entrypoint: /bin/sh
command:
- -c
- |
eval "echo \"$(cat /config/configuration.template.yml)\"" > /config/configuration.yml
exec /app/entrypoint.sh
environment:
TZ: "${TZ}"
DOMAIN: "${DOMAIN}"
DOMAIN_PREFIX: "${DOMAIN_PREFIX}"
AUTHELIA_DB_NAME: "${AUTHELIA_DB_NAME}"
AUTHELIA_DB_USER: "${AUTHELIA_DB_USER}"
AUTHELIA_DB_PASSWORD: "${AUTHELIA_DB_PASSWORD}"
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.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.authelia.entrypoints=websecure"
- "traefik.http.routers.authelia.tls.certresolver=le"
- "traefik.http.routers.authelia.middlewares=security-headers@docker"
- "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_DB_NAME}
POSTGRES_USER: ${AUTHELIA_DB_USER}
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
@@ -133,34 +197,35 @@ 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.entrypoints=websecure
- traefik.http.routers.portainer.tls.certresolver=le
- traefik.http.routers.portainer.middlewares=security-headers
- traefik.http.services.portainer.loadbalancer.server.port=9000
- "traefik.enable=true"
- "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@docker"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
## ─────────────────────────────────────────────
## Uptime Kuma — status page / checks
## ─────────────────────────────────────────────
uptime-kuma:
image: louislam/uptime-kuma:Latest
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.gate.${DOMAIN}`)
- traefik.http.routers.kuma.entrypoints=websecure
- traefik.http.routers.kuma.tls.certresolver=le
- traefik.http.routers.kuma.middlewares=basic-auth,security-headers
- traefik.http.services.kuma.loadbalancer.server.port=3001
- "traefik.enable=true"
- "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@docker"
- "traefik.http.services.kuma.loadbalancer.server.port=3001"
## ─────────────────────────────────────────────
## Umami — web analytics
@@ -177,12 +242,18 @@ services:
depends_on:
- umami-db
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=security-headers
- traefik.http.services.umami.loadbalancer.server.port=3000
- "traefik.enable=true"
- "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@docker"
- "traefik.http.services.umami.loadbalancer.server.port=3000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Umami Database — PostgreSQL
@@ -198,6 +269,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
@@ -212,18 +289,141 @@ 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.entrypoints=websecure
- traefik.http.routers.pgadmin.tls.certresolver=le
- traefik.http.services.pgadmin.loadbalancer.server.port=80
- "traefik.enable=true"
- "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@docker"
- "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
## ─────────────────────────────────────────────
## Beszel Hub — lightweight server monitoring
## ─────────────────────────────────────────────
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- beszel_data:/beszel_data
labels:
- "traefik.enable=true"
- "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@docker"
- "traefik.http.services.beszel.loadbalancer.server.port=8090"
## ─────────────────────────────────────────────
## Gitea — self-hosted Git service
## ─────────────────────────────────────────────
gitea:
image: docker.gitea.com/gitea:latest
container_name: gitea
restart: unless-stopped
networks: [traefik_proxy]
environment:
- 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__database__SSL_MODE=disable
- 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}
- GITEA__i18n__LANGS=en-US
- GITEA__i18n__NAMES=English
volumes:
- 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.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@docker,authelia@docker"
- "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
## ─────────────────────────────────────────────
## Gitea Database — PostgreSQL
## ─────────────────────────────────────────────
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:
- 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
## ─────────────────────────────────────────────
## Duplicati — encrypted cloud backup
## ─────────────────────────────────────────────
duplicati:
image: lscr.io/linuxserver/duplicati:latest
container_name: duplicati
restart: unless-stopped
networks: [traefik_proxy]
environment:
- PUID=0
- PGID=0
- TZ=${TZ}
- SETTINGS_ENCRYPTION_KEY=${DUPLICATI_ENCRYPTION_KEY}
- CLI_ARGS=--webservice-allowed-hostnames=* --webservice-password=${DUPLICATI_PASSWORD}
volumes:
- duplicati_config:/config
- /:/source:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.duplicati.rule=Host(`backup.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.duplicati.entrypoints=websecure"
- "traefik.http.routers.duplicati.tls.certresolver=le"
- "traefik.http.routers.duplicati.middlewares=security-headers@docker"
- "traefik.http.services.duplicati.loadbalancer.server.port=8200"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8200"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s