feat: add PHP ini management system and update infrastructure configs

- Add PHP ini management classes (Access, IniDirective, IniKey, PhpIni)
- Update deployment configurations (Wireguard, Traefik, Monitoring)
- Add DNS stack and Ansible role
- Add deployment debugging playbooks
- Update framework components (FilePath, RedisConnectionPool)
- Update .gitignore and documentation
This commit is contained in:
2025-11-02 15:29:41 +01:00
parent e628d30fa0
commit edcf509a4f
29 changed files with 926 additions and 39 deletions

View File

@@ -10,6 +10,7 @@
wireguard_config_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}.conf"
wireguard_client_configs_path: "/etc/wireguard/clients"
wireguard_local_client_configs_dir: "{{ playbook_dir }}/../wireguard-clients"
wireguard_dns_servers: []
pre_tasks:
- name: Set WireGuard network
@@ -61,6 +62,11 @@
set_fact:
server_vpn_ip: "{{ (wireguard_server_config_read.content | b64decode | regex_search('Address = ([0-9.]+)', '\\1')) | first | default('10.8.0.1') }}"
- name: Set default DNS servers if not provided
set_fact:
wireguard_dns_servers: "{{ [server_vpn_ip] }}"
when: wireguard_dns_servers | length == 0
- name: Extract WireGuard server IP octets
set_fact:
wireguard_server_ip_octets: "{{ server_vpn_ip.split('.') }}"

View File

@@ -0,0 +1,147 @@
---
- name: Check Production Server Status
hosts: production
gather_facts: yes
become: no
tasks:
- name: Check server uptime and basic info
shell: |
echo "=== Server Uptime ==="
uptime
echo ""
echo "=== Disk Space ==="
df -h
echo ""
echo "=== Memory Usage ==="
free -h
echo ""
echo "=== Docker Status ==="
docker --version || echo "Docker not found"
docker ps || echo "Docker not running"
args:
executable: /bin/bash
register: server_info
ignore_errors: yes
failed_when: false
- name: Display server info
debug:
msg: "{{ server_info.stdout_lines }}"
- name: Check all Docker stacks status
shell: |
echo "=== Traefik Stack ==="
cd ~/deployment/stacks/traefik && docker compose ps 2>&1 || echo "Traefik stack not found or not running"
echo ""
echo "=== Application Stack ==="
cd ~/deployment/stacks/application && docker compose ps 2>&1 || echo "Application stack not found or not running"
echo ""
echo "=== PostgreSQL Stack ==="
cd ~/deployment/stacks/postgresql && docker compose ps 2>&1 || echo "PostgreSQL stack not found or not running"
echo ""
echo "=== Monitoring Stack ==="
cd ~/deployment/stacks/monitoring && docker compose ps 2>&1 || echo "Monitoring stack not found or not running"
echo ""
echo "=== Gitea Stack ==="
cd ~/deployment/stacks/gitea && docker compose ps 2>&1 || echo "Gitea stack not found or not running"
echo ""
echo "=== Registry Stack ==="
cd ~/deployment/stacks/registry && docker compose ps 2>&1 || echo "Registry stack not found or not running"
args:
executable: /bin/bash
register: stacks_status
ignore_errors: yes
failed_when: false
- name: Display stacks status
debug:
msg: "{{ stacks_status.stdout_lines }}"
- name: Check Traefik logs for errors
shell: |
cd ~/deployment/stacks/traefik
echo "=== Traefik Logs (Last 30 lines) ==="
docker compose logs --tail=30 traefik 2>&1 | tail -30 || echo "Could not read Traefik logs"
args:
executable: /bin/bash
register: traefik_logs
ignore_errors: yes
failed_when: false
- name: Display Traefik logs
debug:
msg: "{{ traefik_logs.stdout_lines }}"
- name: Check Application stack logs
shell: |
cd ~/deployment/stacks/application
echo "=== Application Nginx Logs (Last 20 lines) ==="
docker compose logs --tail=20 web 2>&1 | tail -20 || echo "Could not read web logs"
echo ""
echo "=== Application PHP Logs (Last 20 lines) ==="
docker compose logs --tail=20 php 2>&1 | tail -20 || echo "Could not read PHP logs"
args:
executable: /bin/bash
register: app_logs
ignore_errors: yes
failed_when: false
- name: Display application logs
debug:
msg: "{{ app_logs.stdout_lines }}"
- name: Test HTTP connectivity
shell: |
echo "=== Testing HTTP Connectivity ==="
echo "Test 1: HTTPS to michaelschiemer.de"
curl -k -H "User-Agent: Mozilla/5.0" -s -o /dev/null -w "HTTP %{http_code}\n" https://michaelschiemer.de/health || echo "Connection failed"
echo ""
echo "Test 2: Direct localhost"
curl -k -H "User-Agent: Mozilla/5.0" -s -o /dev/null -w "HTTP %{http_code}\n" https://localhost/health || echo "Connection failed"
args:
executable: /bin/bash
register: http_tests
ignore_errors: yes
failed_when: false
- name: Display HTTP test results
debug:
msg: "{{ http_tests.stdout_lines }}"
- name: Check network connectivity
shell: |
echo "=== Network Interfaces ==="
ip addr show | grep -E "(inet |state)" | head -10
echo ""
echo "=== Docker Networks ==="
docker network ls
echo ""
echo "=== Traefik Network Connectivity ==="
docker network inspect traefik-public 2>&1 | grep -E "(Name|Subnet|Containers)" | head -10 || echo "Traefik network not found"
args:
executable: /bin/bash
register: network_info
ignore_errors: yes
failed_when: false
- name: Display network info
debug:
msg: "{{ network_info.stdout_lines }}"
- name: Check firewall status
shell: |
echo "=== Firewall Status ==="
sudo ufw status || echo "UFW not installed or not configured"
echo ""
echo "=== Listening Ports ==="
sudo netstat -tlnp | grep -E "(80|443|8080|3000)" | head -10 || ss -tlnp | grep -E "(80|443|8080|3000)" | head -10 || echo "Could not check listening ports"
args:
executable: /bin/bash
register: firewall_info
ignore_errors: yes
failed_when: false
- name: Display firewall info
debug:
msg: "{{ firewall_info.stdout_lines }}"

View File

@@ -0,0 +1,49 @@
---
- name: Check Staging 500 Error
hosts: production
gather_facts: yes
become: no
tasks:
- name: Get recent PHP errors from staging-app
shell: |
cd ~/deployment/stacks/staging
echo "=== Recent PHP errors (last 50 lines) ==="
docker compose exec -T staging-app tail -100 /var/www/html/storage/logs/php-errors.log 2>&1 | tail -50
args:
executable: /bin/bash
register: php_errors
ignore_errors: yes
failed_when: false
- name: Display PHP errors
debug:
msg: "{{ php_errors.stdout_lines }}"
- name: Get docker compose logs for staging-app
shell: |
cd ~/deployment/stacks/staging
echo "=== Recent staging-app container logs ==="
docker compose logs --tail=50 staging-app 2>&1 | tail -50
args:
executable: /bin/bash
register: container_logs
ignore_errors: yes
failed_when: false
- name: Display container logs
debug:
msg: "{{ container_logs.stdout_lines }}"
- name: Test health endpoint
shell: |
curl -H "User-Agent: Mozilla/5.0" -s https://staging.michaelschiemer.de/health 2>&1
args:
executable: /bin/bash
register: health_test
ignore_errors: yes
failed_when: false
- name: Display health endpoint result
debug:
msg: "{{ health_test.stdout }}"

View File

@@ -0,0 +1,125 @@
---
- name: Debug Grafana 403 Error
hosts: production
gather_facts: yes
become: no
# This playbook requires the production inventory file
# Run with: ansible-playbook -i ../inventory/production.yml debug-grafana-403.yml
tasks:
- name: Check Traefik logs for recent Grafana access attempts
shell: |
cd ~/deployment/stacks/traefik
echo "=== Recent Traefik Access Logs (last 50 lines with grafana) ==="
docker compose logs --tail=100 traefik 2>&1 | grep -i grafana | tail -50 || echo "No grafana entries found"
args:
executable: /bin/bash
register: traefik_logs
ignore_errors: yes
failed_when: false
- name: Display Traefik logs
debug:
msg: "{{ traefik_logs.stdout_lines }}"
- name: Check Traefik access log file
shell: |
cd ~/deployment/stacks/traefik
echo "=== Recent Traefik Access Log (last 50 lines) ==="
tail -50 logs/access.log 2>&1 | tail -50 || echo "Access log not found"
args:
executable: /bin/bash
register: access_log
ignore_errors: yes
failed_when: false
- name: Display access log
debug:
msg: "{{ access_log.stdout_lines }}"
- name: Check Grafana container status
shell: |
cd ~/deployment/stacks/monitoring
docker compose ps grafana
args:
executable: /bin/bash
register: grafana_status
ignore_errors: yes
failed_when: false
- name: Display Grafana status
debug:
msg: "{{ grafana_status.stdout_lines }}"
- name: Check Grafana Traefik labels
shell: |
cd ~/deployment/stacks/monitoring
docker compose config | grep -A 20 "grafana:" | grep -E "(ipwhitelist|middleware|sourcerange)" || echo "No IP whitelist labels found"
args:
executable: /bin/bash
register: grafana_labels
ignore_errors: yes
failed_when: false
- name: Display Grafana labels
debug:
msg: "{{ grafana_labels.stdout_lines }}"
- name: Check CoreDNS configuration
shell: |
cd ~/deployment/stacks/dns
echo "=== CoreDNS Corefile ==="
cat Corefile 2>&1 || echo "Corefile not found"
args:
executable: /bin/bash
register: coredns_config
ignore_errors: yes
failed_when: false
- name: Display CoreDNS configuration
debug:
msg: "{{ coredns_config.stdout_lines }}"
- name: Check monitoring stack environment variables
shell: |
cd ~/deployment/stacks/monitoring
echo "=== MONITORING_VPN_IP_WHITELIST ==="
grep MONITORING_VPN_IP_WHITELIST .env 2>&1 || echo "Variable not found in .env"
args:
executable: /bin/bash
register: monitoring_env
ignore_errors: yes
failed_when: false
- name: Display monitoring environment
debug:
msg: "{{ monitoring_env.stdout_lines }}"
- name: Test DNS resolution for grafana.michaelschiemer.de
shell: |
echo "=== DNS Resolution Test ==="
dig +short grafana.michaelschiemer.de @10.8.0.1 2>&1 || echo "DNS resolution failed"
args:
executable: /bin/bash
register: dns_test
ignore_errors: yes
failed_when: false
- name: Display DNS test result
debug:
msg: "{{ dns_test.stdout_lines }}"
- name: Check WireGuard interface status
shell: |
echo "=== WireGuard Interface Status ==="
sudo wg show 2>&1 || echo "WireGuard not running or no permissions"
args:
executable: /bin/bash
register: wg_status
ignore_errors: yes
failed_when: false
- name: Display WireGuard status
debug:
msg: "{{ wg_status.stdout_lines }}"

View File

@@ -0,0 +1,142 @@
---
- name: Fix Traefik Configuration
hosts: production
gather_facts: no
become: no
tasks:
- name: Backup current traefik.yml
shell: |
cd ~/deployment/stacks/traefik
cp traefik.yml traefik.yml.backup.$(date +%Y%m%d_%H%M%S)
args:
executable: /bin/bash
- name: Create correct traefik.yml
copy:
content: |
# Static Configuration for Traefik
# Global Configuration
global:
checkNewVersion: true
sendAnonymousUsage: false
# API and Dashboard
# Note: insecure: false means API is only accessible via HTTPS (through Traefik itself)
# No port 8080 needed - dashboard accessible via HTTPS at traefik.michaelschiemer.de
api:
dashboard: true
insecure: false
# Dashboard accessible via HTTPS router (no separate HTTP listener needed)
# 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"
# 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 mode is 'host', so we don't specify a network here
# Traefik can reach containers directly via their IPs in host network mode
watch: true
file:
directory: /dynamic
watch: true
# Forwarded Headers Configuration
# This ensures Traefik correctly identifies the real client IP
# Important for VPN access where requests come from WireGuard interface
forwardedHeaders:
trustedIPs:
- "10.8.0.0/24" # WireGuard VPN network
- "127.0.0.1/32" # Localhost
- "172.17.0.0/16" # Docker bridge network
- "172.18.0.0/16" # Docker user-defined networks
insecure: false
# 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
dest: ~/deployment/stacks/traefik/traefik.yml
mode: '0644'
- name: Validate YAML syntax
command: python3 -c "import yaml; yaml.safe_load(open('traefik.yml')); print('YAML valid')"
args:
chdir: ~/deployment/stacks/traefik
changed_when: false
- name: Restart Traefik
command: docker compose up -d traefik
args:
chdir: ~/deployment/stacks/traefik
register: traefik_restart
- name: Wait for Traefik to start
pause:
seconds: 5
- name: Check Traefik status
command: docker compose ps traefik
args:
chdir: ~/deployment/stacks/traefik
register: traefik_status
- name: Display Traefik status
debug:
msg: "{{ traefik_status.stdout_lines }}"

View File

@@ -59,17 +59,22 @@
import_role:
name: registry
# 4. Deploy MinIO (Object Storage)
# 4. Deploy DNS (CoreDNS for WireGuard clients)
- name: Deploy DNS stack
import_role:
name: dns
# 5. Deploy MinIO (Object Storage)
- name: Deploy MinIO stack
import_role:
name: minio
# 5. Deploy Gitea (CRITICAL - Git Server + MySQL + Redis)
# 6. Deploy Gitea (CRITICAL - Git Server + MySQL + Redis)
- name: Deploy Gitea stack
import_role:
name: gitea
# 6. Deploy Monitoring (Portainer + Grafana + Prometheus)
# 7. Deploy Monitoring (Portainer + Grafana + Prometheus)
- name: Deploy Monitoring stack
import_role:
name: monitoring
@@ -98,7 +103,7 @@
debug:
msg: "Gitea HTTPS check: {{ 'SUCCESS' if gitea_http_check.status == 200 else 'FAILED - Status: ' + (gitea_http_check.status|string) }}"
# 7. Deploy Application Stack
# 8. Deploy Application Stack
- name: Deploy Application Stack
import_role:
name: application
@@ -131,6 +136,7 @@
- "Traefik: {{ 'Deployed' if traefik_stack_changed else 'Already running' }}"
- "PostgreSQL: {{ 'Deployed' if postgresql_stack_changed else 'Already running' }}"
- "Docker Registry: {{ 'Deployed' if registry_stack_changed else 'Already running' }}"
- "DNS: {{ 'Deployed' if dns_stack_changed else 'Already running' }}"
- "MinIO: {{ 'Deployed' if minio_stack_changed else 'Already running' }}"
- "Gitea: {{ 'Deployed' if gitea_stack_changed else 'Already running' }}"
- "Monitoring: {{ 'Deployed' if monitoring_stack_changed else 'Already running' }}"

View File

@@ -0,0 +1,9 @@
---
dns_stack_path: "{{ stacks_base_path }}/dns"
dns_corefile_template: "{{ role_path }}/../../templates/dns-Corefile.j2"
dns_forwarders:
- 1.1.1.1
- 8.8.8.8
dns_records:
- host: "grafana.{{ app_domain }}"
address: "{{ wireguard_server_ip_default | default('10.8.0.1') }}"

View File

@@ -0,0 +1,33 @@
---
- name: Ensure DNS stack directory exists
file:
path: "{{ dns_stack_path }}"
state: directory
mode: '0755'
tags:
- dns
- name: Render CoreDNS configuration
template:
src: "{{ dns_corefile_template }}"
dest: "{{ dns_stack_path }}/Corefile"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
tags:
- dns
- name: Deploy DNS stack
community.docker.docker_compose_v2:
project_src: "{{ dns_stack_path }}"
state: present
pull: always
register: dns_compose_result
tags:
- dns
- name: Record DNS deployment facts
set_fact:
dns_stack_changed: "{{ dns_compose_result.changed | default(false) }}"
tags:
- dns

View File

@@ -3,4 +3,5 @@ monitoring_stack_path: "{{ stacks_base_path }}/monitoring"
monitoring_wait_timeout: "{{ wait_timeout | default(60) }}"
monitoring_env_template: "{{ role_path }}/../../templates/monitoring.env.j2"
monitoring_vault_file: "{{ role_path }}/../../secrets/production.vault.yml"
monitoring_vpn_ip_whitelist: "{{ wireguard_network_default | default('10.8.0.0/24') }}"
# VPN IP whitelist: Allow WireGuard VPN network only (override via extra vars if needed)
monitoring_vpn_ip_whitelist: "{{ monitoring_vpn_ip_whitelist_ranges | default([wireguard_network_default | default('10.8.0.0/24')]) | join(',') }}"

View File

@@ -15,6 +15,7 @@
no_log: yes
delegate_to: localhost
become: no
ignore_errors: yes
tags:
- monitoring
@@ -48,6 +49,36 @@
tags:
- monitoring
- name: Build VPN IP whitelist with endpoints
set_fact:
monitoring_vpn_ip_whitelist_ranges: "{{ [wireguard_network_default | default('10.8.0.0/24')] }}"
tags:
- monitoring
- name: Set VPN IP whitelist for monitoring
set_fact:
monitoring_vpn_ip_whitelist: "{{ monitoring_vpn_ip_whitelist_ranges | join(',') }}"
tags:
- monitoring
- name: Set Traefik stack path
set_fact:
traefik_stack_path: "{{ stacks_base_path }}/traefik"
tags:
- monitoring
- name: Update Traefik middleware with dynamic VPN IPs
template:
src: "{{ role_path }}/../../templates/traefik-middlewares.yml.j2"
dest: "{{ traefik_stack_path }}/dynamic/middlewares.yml"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
vars:
vpn_network: "{{ wireguard_network_default | default('10.8.0.0/24') }}"
tags:
- monitoring
- name: Ensure monitoring stack directory exists
file:
path: "{{ monitoring_stack_path }}"

View File

@@ -0,0 +1,15 @@
. {
log
errors
health :8053
hosts {
{% for record in dns_records %}
{{ record.address }} {{ record.host }}
{% endfor %}
fallthrough
}
{% if dns_forwarders | length > 0 %}
forward . {{ dns_forwarders | join(' ') }}
{% endif %}
cache 30
}

View File

@@ -0,0 +1,82 @@
# 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"
# VPN-only IP whitelist for Grafana and other monitoring services
# Restrict access strictly to the WireGuard VPN network
grafana-vpn-only:
ipWhiteList:
sourceRange:
- "{{ vpn_network }}" # WireGuard VPN network
# VPN-only IP whitelist for general use (Traefik Dashboard, etc.)
# Restrict access strictly to the WireGuard network
vpn-only:
ipWhiteList:
sourceRange:
- "{{ vpn_network }}" # WireGuard VPN network
# 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

View File

@@ -8,8 +8,8 @@ PrivateKey = {{ client_private_key.stdout }}
# Client IP address in VPN network
Address = {{ client_ip }}/24
# DNS server (optional)
DNS = 1.1.1.1, 8.8.8.8
# DNS server (VPN internal resolver)
DNS = {{ wireguard_dns_servers | join(', ') }}
[Peer]
# Server public key
@@ -24,4 +24,4 @@ Endpoint = {{ server_external_ip_content }}:{{ wireguard_port }}
AllowedIPs = {{ allowed_ips }}
# Keep connection alive
PersistentKeepalive = 25
PersistentKeepalive = 25

View File

@@ -3,13 +3,13 @@
[Interface]
# Client private key
PrivateKey = uMhNKh+Wi0aykTnazfSJD6l7Wc2V1Pe+7rFtFcnfynw=
PrivateKey = sE81MBr64fP8YBDlhRWngwHHmlrVzIhs9NT7Dh7XbVs=
# Client IP address in VPN network
Address = 10.8.0.4/24
Address = 10.8.0.7/24
# DNS server (optional)
DNS = 1.1.1.1, 8.8.8.8
# DNS server (VPN internal resolver)
DNS = 10.8.0.1
[Peer]
# Server public key
@@ -24,4 +24,4 @@ Endpoint = 94.16.110.151:51820
AllowedIPs = 10.8.0.0/24
# Keep connection alive
PersistentKeepalive = 25
PersistentKeepalive = 25

View File

@@ -0,0 +1,14 @@
services:
coredns:
image: coredns/coredns:1.11.1
container_name: coredns
restart: unless-stopped
network_mode: host
command: -conf /etc/coredns/Corefile
volumes:
- ./Corefile:/etc/coredns/Corefile:ro
healthcheck:
# Disable healthcheck - CoreDNS is a minimal image without shell
# CoreDNS runs fine (verified by DNS queries working correctly)
# If needed, health can be checked externally via dig
disable: true

View File

@@ -75,8 +75,9 @@ services:
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls=true"
- "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.grafana-vpn-only.ipwhitelist.sourcerange=${MONITORING_VPN_IP_WHITELIST:-10.8.0.0/24}"
- "traefik.http.routers.grafana.middlewares=grafana-vpn-only"
# VPN IP whitelist: Use middleware defined in Traefik dynamic config
# Middleware is defined in deployment/stacks/traefik/dynamic/middlewares.yml
- "traefik.http.routers.grafana.middlewares=grafana-vpn-only@file"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
depends_on:
prometheus:

View File

@@ -11,7 +11,10 @@ Traefik acts as the central reverse proxy for all services, handling:
## Services
- **traefik.michaelschiemer.de** - Traefik Dashboard (BasicAuth protected)
- **traefik.michaelschiemer.de** - Traefik Dashboard (VPN-only + BasicAuth protected)
- ?? **Nur ?ber WireGuard VPN erreichbar** (10.8.0.0/24)
- Zus?tzlich durch BasicAuth gesch?tzt
- ?ffentlicher Zugriff ist blockiert
## Prerequisites
@@ -126,6 +129,16 @@ labels:
- "traefik.http.routers.myapp.middlewares=gzip-compression@file"
```
### VPN-Only Access (WireGuard Network)
```yaml
labels:
# Restrict access to WireGuard VPN network only (10.8.0.0/24)
- "traefik.http.routers.myapp.middlewares=vpn-only@file"
# Combined: VPN-only + BasicAuth (order matters - VPN check first, then BasicAuth)
- "traefik.http.routers.myapp.middlewares=vpn-only@file,traefik-auth"
```
### Middleware Chains
```yaml
labels:

View File

@@ -5,11 +5,14 @@ services:
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- traefik-public
ports:
- "80:80"
- "443:443"
# Use host network mode to correctly identify client IPs from WireGuard
# Without this, Traefik sees Docker bridge IPs instead of real client IPs (10.8.0.x)
network_mode: host
# When using host network mode, we don't bind ports in docker-compose
# Traefik listens directly on host ports 80 and 443
# ports:
# - "80:80"
# - "443:443"
environment:
- TZ=Europe/Berlin
volumes:
@@ -27,13 +30,15 @@ services:
# Enable Traefik for itself
- "traefik.enable=true"
# Dashboard
# Dashboard - VPN-only access (WireGuard network required)
# Accessible only from WireGuard VPN network (10.8.0.0/24)
- "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"
# VPN-only + BasicAuth protection (order: vpn-only first, then BasicAuth)
- "traefik.http.routers.traefik-dashboard.middlewares=vpn-only@file,traefik-auth"
# BasicAuth for dashboard (user: admin, password: generate with htpasswd)
# htpasswd -nb admin your_password
@@ -73,6 +78,5 @@ services:
retries: 3
start_period: 10s
networks:
traefik-public:
external: true
# Note: network_mode: host is used, so we don't define networks here
# Traefik still discovers services via Docker labels using the Docker socket

View File

@@ -51,6 +51,20 @@ http:
# sourceRange:
# - "127.0.0.1/32"
# - "10.0.0.0/8"
# VPN-only IP whitelist for Grafana and other monitoring services
# Restrict access strictly to the WireGuard network
grafana-vpn-only:
ipWhiteList:
sourceRange:
- "10.8.0.0/24" # WireGuard VPN network
# VPN-only IP whitelist for general use (Traefik Dashboard, etc.)
# Restrict access strictly to the WireGuard network
vpn-only:
ipWhiteList:
sourceRange:
- "10.8.0.0/24" # WireGuard VPN network
# Chain multiple middlewares
default-chain:

View File

@@ -6,9 +6,12 @@ global:
sendAnonymousUsage: false
# API and Dashboard
# Note: insecure: false means API is only accessible via HTTPS (through Traefik itself)
# No port 8080 needed - dashboard accessible via HTTPS at traefik.michaelschiemer.de
api:
dashboard: true
insecure: false
# Dashboard accessible via HTTPS router (no separate HTTP listener needed)
# Entry Points
entryPoints:
@@ -26,9 +29,6 @@ entryPoints:
- main: michaelschiemer.de
sans:
- "*.michaelschiemer.de"
middlewares:
- security-headers@docker
- compression@docker
# Certificate Resolvers
certificatesResolvers:
@@ -50,13 +50,25 @@ providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik-public
# Network mode is 'host', so we don't specify a network here
# Traefik can reach containers directly via their IPs in host network mode
watch: true
file:
directory: /dynamic
watch: true
# Forwarded Headers Configuration
# This ensures Traefik correctly identifies the real client IP
# Important for VPN access where requests come from WireGuard interface
forwardedHeaders:
trustedIPs:
- "10.8.0.0/24" # WireGuard VPN network
- "127.0.0.1/32" # Localhost
- "172.17.0.0/16" # Docker bridge network
- "172.18.0.0/16" # Docker user-defined networks
insecure: false
# Logging
log:
level: INFO