feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready
This commit is contained in:
20
deployment/stacks/traefik/.env.example
Normal file
20
deployment/stacks/traefik/.env.example
Normal file
@@ -0,0 +1,20 @@
|
||||
# Traefik Configuration
|
||||
# Copy this file to .env and adjust values
|
||||
|
||||
# Timezone
|
||||
TZ=Europe/Berlin
|
||||
|
||||
# Let's Encrypt Email
|
||||
ACME_EMAIL=kontakt@michaelschiemer.de
|
||||
|
||||
# Domain
|
||||
DOMAIN=michaelschiemer.de
|
||||
|
||||
# Dashboard Authentication
|
||||
# Generate password hash with: htpasswd -nb admin your_password
|
||||
# Replace $ with $$ in docker-compose.yml
|
||||
TRAEFIK_DASHBOARD_USER=admin
|
||||
TRAEFIK_DASHBOARD_PASSWORD_HASH=
|
||||
|
||||
# Log Level (DEBUG, INFO, WARN, ERROR)
|
||||
LOG_LEVEL=INFO
|
||||
372
deployment/stacks/traefik/README.md
Normal file
372
deployment/stacks/traefik/README.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# Traefik Stack - Reverse Proxy with SSL
|
||||
|
||||
## Overview
|
||||
|
||||
Traefik acts as the central reverse proxy for all services, handling:
|
||||
- Automatic SSL certificate generation via Let's Encrypt
|
||||
- HTTP to HTTPS redirection
|
||||
- Service discovery via Docker labels
|
||||
- Security headers and compression
|
||||
- Rate limiting and access control
|
||||
|
||||
## Services
|
||||
|
||||
- **traefik.michaelschiemer.de** - Traefik Dashboard (BasicAuth protected)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Docker Network**
|
||||
```bash
|
||||
docker network create traefik-public
|
||||
```
|
||||
|
||||
2. **ACME Storage File**
|
||||
```bash
|
||||
touch acme.json
|
||||
chmod 600 acme.json
|
||||
```
|
||||
|
||||
3. **DNS Configuration**
|
||||
Point these domains to your server IP (94.16.110.151):
|
||||
- `michaelschiemer.de`
|
||||
- `*.michaelschiemer.de` (wildcard)
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Create Environment File
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 2. Generate Dashboard Password
|
||||
|
||||
```bash
|
||||
# Generate password hash
|
||||
htpasswd -nb admin your_secure_password
|
||||
|
||||
# Example output:
|
||||
# admin:$apr1$8kj9d7lj$r.x5jhLVPLuCDLvJ6x0Hd0
|
||||
|
||||
# Important: In docker-compose.yml, replace $ with $$
|
||||
# admin:$$apr1$$8kj9d7lj$$r.x5jhLVPLuCDLvJ6x0Hd0
|
||||
```
|
||||
|
||||
Update the `traefik.http.middlewares.traefik-auth.basicauth.users` label in `docker-compose.yml`.
|
||||
|
||||
### 3. Adjust Configuration (Optional)
|
||||
|
||||
Edit `traefik.yml` for:
|
||||
- Log levels
|
||||
- Certificate resolvers
|
||||
- Additional entry points
|
||||
- Metrics configuration
|
||||
|
||||
## Deployment
|
||||
|
||||
### Initial Setup
|
||||
|
||||
```bash
|
||||
# Create network
|
||||
docker network create traefik-public
|
||||
|
||||
# Create acme.json
|
||||
touch acme.json
|
||||
chmod 600 acme.json
|
||||
|
||||
# Create log directories
|
||||
mkdir -p logs
|
||||
|
||||
# Start Traefik
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Verify Deployment
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker compose ps
|
||||
|
||||
# Check logs
|
||||
docker compose logs -f
|
||||
|
||||
# Test dashboard access
|
||||
curl -I https://traefik.michaelschiemer.de
|
||||
|
||||
# Check certificate
|
||||
openssl s_client -connect traefik.michaelschiemer.de:443 -servername traefik.michaelschiemer.de < /dev/null
|
||||
```
|
||||
|
||||
## Middleware Configuration
|
||||
|
||||
Traefik provides several reusable middlewares in `dynamic/middlewares.yml`:
|
||||
|
||||
### Security Headers
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.myapp.middlewares=security-headers-global@file"
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
```yaml
|
||||
labels:
|
||||
# Strict: 50 req/s
|
||||
- "traefik.http.routers.myapp.middlewares=rate-limit-strict@file"
|
||||
|
||||
# Moderate: 100 req/s
|
||||
- "traefik.http.routers.myapp.middlewares=rate-limit-moderate@file"
|
||||
|
||||
# Lenient: 200 req/s
|
||||
- "traefik.http.routers.myapp.middlewares=rate-limit-lenient@file"
|
||||
```
|
||||
|
||||
### Compression
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.myapp.middlewares=gzip-compression@file"
|
||||
```
|
||||
|
||||
### Middleware Chains
|
||||
```yaml
|
||||
labels:
|
||||
# Default chain: Security + Compression
|
||||
- "traefik.http.routers.myapp.middlewares=default-chain@file"
|
||||
|
||||
# Admin chain: Security + Compression + Rate Limiting
|
||||
- "traefik.http.routers.myapp.middlewares=admin-chain@file"
|
||||
```
|
||||
|
||||
## Service Integration
|
||||
|
||||
### Example Service Configuration
|
||||
|
||||
Add these labels to any Docker service to expose it through Traefik:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
myapp:
|
||||
image: myapp:latest
|
||||
networks:
|
||||
- traefik-public
|
||||
labels:
|
||||
# Enable Traefik
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Router configuration
|
||||
- "traefik.http.routers.myapp.rule=Host(`app.michaelschiemer.de`)"
|
||||
- "traefik.http.routers.myapp.entrypoints=websecure"
|
||||
- "traefik.http.routers.myapp.tls=true"
|
||||
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
|
||||
|
||||
# Service configuration
|
||||
- "traefik.http.services.myapp.loadbalancer.server.port=80"
|
||||
|
||||
# Middleware (optional)
|
||||
- "traefik.http.routers.myapp.middlewares=default-chain@file"
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Dashboard Access
|
||||
|
||||
Access the Traefik dashboard at: https://traefik.michaelschiemer.de
|
||||
|
||||
Default credentials (change in production):
|
||||
- Username: `admin`
|
||||
- Password: (set via htpasswd)
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# Access logs (HTTP requests)
|
||||
tail -f logs/access.log
|
||||
|
||||
# Traefik logs (errors, warnings)
|
||||
tail -f logs/traefik.log
|
||||
|
||||
# Container logs
|
||||
docker compose logs -f traefik
|
||||
```
|
||||
|
||||
### Prometheus Metrics
|
||||
|
||||
Traefik exposes Prometheus metrics for monitoring:
|
||||
|
||||
```yaml
|
||||
# Add to Prometheus scrape config
|
||||
- job_name: 'traefik'
|
||||
static_configs:
|
||||
- targets: ['traefik:8082']
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Certificate Issues
|
||||
|
||||
```bash
|
||||
# Check acme.json permissions
|
||||
ls -la acme.json
|
||||
# Should be: -rw------- (600)
|
||||
|
||||
# View certificate status
|
||||
docker compose logs traefik | grep -i "certificate"
|
||||
|
||||
# Force certificate renewal
|
||||
rm acme.json
|
||||
touch acme.json
|
||||
chmod 600 acme.json
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
### DNS Issues
|
||||
|
||||
```bash
|
||||
# Verify DNS resolution
|
||||
dig michaelschiemer.de
|
||||
dig git.michaelschiemer.de
|
||||
|
||||
# Check from external
|
||||
nslookup michaelschiemer.de 8.8.8.8
|
||||
```
|
||||
|
||||
### Service Not Accessible
|
||||
|
||||
```bash
|
||||
# Check Traefik can reach service
|
||||
docker network inspect traefik-public
|
||||
|
||||
# Verify service labels
|
||||
docker inspect <container_name> | grep -A 20 Labels
|
||||
|
||||
# Check Traefik logs for routing errors
|
||||
docker compose logs traefik | grep -i error
|
||||
```
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
```bash
|
||||
# Check if ports 80/443 are free
|
||||
sudo netstat -tlnp | grep -E ':80|:443'
|
||||
|
||||
# Stop conflicting services
|
||||
sudo systemctl stop nginx # or apache2
|
||||
```
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### 1. IP Whitelisting
|
||||
|
||||
Uncomment and configure in `dynamic/middlewares.yml`:
|
||||
|
||||
```yaml
|
||||
admin-whitelist:
|
||||
ipWhiteList:
|
||||
sourceRange:
|
||||
- "your.vpn.ip.range/32"
|
||||
- "10.0.0.0/8"
|
||||
```
|
||||
|
||||
### 2. Strong Dashboard Password
|
||||
|
||||
```bash
|
||||
# Generate strong password
|
||||
openssl rand -base64 32
|
||||
|
||||
# Create hash
|
||||
htpasswd -nb admin "your_strong_password"
|
||||
```
|
||||
|
||||
### 3. Rate Limiting
|
||||
|
||||
Apply rate limiting to sensitive endpoints:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.admin.middlewares=rate-limit-strict@file"
|
||||
```
|
||||
|
||||
### 4. DDoS Protection
|
||||
|
||||
```yaml
|
||||
# In traefik.yml - add entry point middleware
|
||||
entryPoints:
|
||||
websecure:
|
||||
address: ":443"
|
||||
http:
|
||||
middlewares:
|
||||
- rate-limit-moderate@file
|
||||
```
|
||||
|
||||
## Backup
|
||||
|
||||
### Important Files
|
||||
|
||||
- `acme.json` - SSL certificates
|
||||
- `traefik.yml` - Static configuration
|
||||
- `dynamic/` - Dynamic configuration
|
||||
|
||||
```bash
|
||||
# Backup certificates
|
||||
cp acme.json acme.json.backup.$(date +%Y%m%d)
|
||||
|
||||
# Backup configuration
|
||||
tar -czf traefik-config-backup.tar.gz traefik.yml dynamic/
|
||||
```
|
||||
|
||||
## Updates
|
||||
|
||||
```bash
|
||||
# Pull latest image
|
||||
docker compose pull
|
||||
|
||||
# Restart with new image
|
||||
docker compose up -d
|
||||
|
||||
# Verify
|
||||
docker compose ps
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Connection Limits
|
||||
|
||||
In `traefik.yml`:
|
||||
|
||||
```yaml
|
||||
entryPoints:
|
||||
websecure:
|
||||
transport:
|
||||
respondingTimeouts:
|
||||
readTimeout: 60s
|
||||
writeTimeout: 60s
|
||||
lifeCycle:
|
||||
requestAcceptGraceTimeout: 0s
|
||||
graceTimeOut: 10s
|
||||
```
|
||||
|
||||
### Resource Limits
|
||||
|
||||
In `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: '0.25'
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For issues with Traefik configuration:
|
||||
1. Check official Traefik documentation: https://doc.traefik.io/traefik/
|
||||
2. Review logs: `docker compose logs -f`
|
||||
3. Verify network connectivity: `docker network inspect traefik-public`
|
||||
80
deployment/stacks/traefik/docker-compose.yml
Normal file
80
deployment/stacks/traefik/docker-compose.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- traefik-public
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
volumes:
|
||||
# Docker socket for service discovery
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
# Static configuration
|
||||
- ./traefik.yml:/traefik.yml:ro
|
||||
# Dynamic configuration
|
||||
- ./dynamic:/dynamic:ro
|
||||
# SSL certificates
|
||||
- ./acme.json:/acme.json
|
||||
# Logs
|
||||
- ./logs:/logs
|
||||
labels:
|
||||
# Enable Traefik for itself
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Dashboard
|
||||
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.michaelschiemer.de`)"
|
||||
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik-dashboard.tls=true"
|
||||
- "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.traefik-dashboard.service=api@internal"
|
||||
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-auth"
|
||||
|
||||
# BasicAuth for dashboard (user: admin, password: generate with htpasswd)
|
||||
# htpasswd -nb admin your_password
|
||||
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$8kj9d7lj$$r.x5jhLVPLuCDLvJ6x0Hd0"
|
||||
|
||||
# Allow ACME challenges without redirect (higher priority)
|
||||
- "traefik.http.routers.acme-challenge.rule=PathPrefix(`/.well-known/acme-challenge`)"
|
||||
- "traefik.http.routers.acme-challenge.entrypoints=web"
|
||||
- "traefik.http.routers.acme-challenge.priority=200"
|
||||
|
||||
# Global redirect to HTTPS (lower priority, matches everything else)
|
||||
- "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
|
||||
- "traefik.http.routers.http-catchall.entrypoints=web"
|
||||
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
|
||||
- "traefik.http.routers.http-catchall.priority=1"
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
|
||||
|
||||
# Security headers middleware
|
||||
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
|
||||
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
|
||||
|
||||
# Compression middleware
|
||||
- "traefik.http.middlewares.compression.compress=true"
|
||||
|
||||
# Rate limiting middleware (100 requests per second)
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
|
||||
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
|
||||
healthcheck:
|
||||
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
15
deployment/stacks/traefik/dynamic/gitea.yml
Normal file
15
deployment/stacks/traefik/dynamic/gitea.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
http:
|
||||
routers:
|
||||
gitea:
|
||||
rule: Host(`git.michaelschiemer.de`)
|
||||
entrypoints:
|
||||
- websecure
|
||||
service: gitea
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 100
|
||||
services:
|
||||
gitea:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://gitea:3000
|
||||
68
deployment/stacks/traefik/dynamic/middlewares.yml
Normal file
68
deployment/stacks/traefik/dynamic/middlewares.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
# Dynamic Middleware Configuration
|
||||
|
||||
http:
|
||||
middlewares:
|
||||
# Security headers for all services
|
||||
security-headers-global:
|
||||
headers:
|
||||
frameDeny: true
|
||||
contentTypeNosniff: true
|
||||
browserXssFilter: true
|
||||
stsSeconds: 31536000
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
forceSTSHeader: true
|
||||
customFrameOptionsValue: "SAMEORIGIN"
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"
|
||||
referrerPolicy: "strict-origin-when-cross-origin"
|
||||
permissionsPolicy: "geolocation=(), microphone=(), camera=()"
|
||||
|
||||
# Compression for better performance
|
||||
gzip-compression:
|
||||
compress:
|
||||
excludedContentTypes:
|
||||
- text/event-stream
|
||||
|
||||
# Rate limiting - strict
|
||||
rate-limit-strict:
|
||||
rateLimit:
|
||||
average: 50
|
||||
burst: 25
|
||||
period: 1s
|
||||
|
||||
# Rate limiting - moderate
|
||||
rate-limit-moderate:
|
||||
rateLimit:
|
||||
average: 100
|
||||
burst: 50
|
||||
period: 1s
|
||||
|
||||
# Rate limiting - lenient
|
||||
rate-limit-lenient:
|
||||
rateLimit:
|
||||
average: 200
|
||||
burst: 100
|
||||
period: 1s
|
||||
|
||||
# IP whitelist for admin services (example)
|
||||
# Uncomment and adjust for production
|
||||
# admin-whitelist:
|
||||
# ipWhiteList:
|
||||
# sourceRange:
|
||||
# - "127.0.0.1/32"
|
||||
# - "10.0.0.0/8"
|
||||
|
||||
# Chain multiple middlewares
|
||||
default-chain:
|
||||
chain:
|
||||
middlewares:
|
||||
- security-headers-global
|
||||
- gzip-compression
|
||||
|
||||
admin-chain:
|
||||
chain:
|
||||
middlewares:
|
||||
- security-headers-global
|
||||
- gzip-compression
|
||||
- rate-limit-strict
|
||||
# - admin-whitelist # Uncomment for IP whitelisting
|
||||
85
deployment/stacks/traefik/traefik.yml
Normal file
85
deployment/stacks/traefik/traefik.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
# Static Configuration for Traefik
|
||||
|
||||
# Global Configuration
|
||||
global:
|
||||
checkNewVersion: true
|
||||
sendAnonymousUsage: false
|
||||
|
||||
# API and Dashboard
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: false
|
||||
|
||||
# Entry Points
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
# No global redirect - ACME challenges need HTTP access
|
||||
# Redirects are handled per-router via middleware
|
||||
|
||||
websecure:
|
||||
address: ":443"
|
||||
http:
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
domains:
|
||||
- main: michaelschiemer.de
|
||||
sans:
|
||||
- "*.michaelschiemer.de"
|
||||
middlewares:
|
||||
- security-headers@docker
|
||||
- compression@docker
|
||||
|
||||
# Certificate Resolvers
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: kontakt@michaelschiemer.de
|
||||
storage: /acme.json
|
||||
caServer: https://acme-v02.api.letsencrypt.org/directory
|
||||
# Use HTTP-01 challenge (requires port 80 accessible)
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
# Uncomment for DNS challenge (requires DNS provider)
|
||||
# dnsChallenge:
|
||||
# provider: cloudflare
|
||||
# delayBeforeCheck: 30
|
||||
|
||||
# Providers
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
exposedByDefault: false
|
||||
network: traefik-public
|
||||
watch: true
|
||||
|
||||
file:
|
||||
directory: /dynamic
|
||||
watch: true
|
||||
|
||||
# Logging
|
||||
log:
|
||||
level: INFO
|
||||
filePath: /logs/traefik.log
|
||||
format: json
|
||||
|
||||
# Access Logs
|
||||
accessLog:
|
||||
filePath: /logs/access.log
|
||||
format: json
|
||||
bufferingSize: 100
|
||||
filters:
|
||||
statusCodes:
|
||||
- "400-499"
|
||||
- "500-599"
|
||||
|
||||
# Metrics
|
||||
metrics:
|
||||
prometheus:
|
||||
addEntryPointsLabels: true
|
||||
addRoutersLabels: true
|
||||
addServicesLabels: true
|
||||
|
||||
# Ping
|
||||
ping:
|
||||
entryPoint: web
|
||||
Reference in New Issue
Block a user