feat(deployment): update Semaphore stack and Traefik configuration

- Add QUICKSTART.md and SETUP_REPOSITORY.md for Semaphore stack
- Add playbooks directory for Semaphore deployment
- Update Semaphore docker-compose.yml, env.example, and README
- Add Traefik local configuration files
- Disable semaphore.yml in Traefik dynamic config
- Update docker-compose.local.yml and build-image workflow
This commit is contained in:
2025-11-02 22:55:51 +01:00
parent 0c4ff1283c
commit 77c656af62
15 changed files with 1280 additions and 77 deletions

View File

@@ -150,7 +150,7 @@ jobs:
NEEDS_BUILD=false NEEDS_BUILD=false
OTHER_NON_IGNORED=false OTHER_NON_IGNORED=false
IGNORE_PATTERN='^(docs/|docs$|tests/|tests$|tests-e2e/|\.github/|\.gitea/|\.idea/|\.vscode/|\.husky/|.*\.md$|.*\.MD$|LICENSE$|CHANGELOG|CHANGELOG\.md$|\.editorconfig$|\.gitignore$)' IGNORE_PATTERN='^(docs/|docs$|tests/|tests$|tests-e2e/|\.github/|\.gitea/|\.idea/|\.vscode/|\.husky/|.*\.md$|.*\.MD$|LICENSE$|CHANGELOG|CHANGELOG\.md$|\.editorconfig$|\.gitignore$)'
BUILD_TRIGGER_PATTERN='^(src/|resources/|config/|app/|public/|composer\.json$|composer\.lock$|composer/|package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|Dockerfile\.production$|Dockerfile\.runtime$|docker-compose\.production\.yml$|docker/|vite\.config\.(js|ts)$|tsconfig\.json$|babel\.config\.js$|Makefile$|artisan$)' BUILD_TRIGGER_PATTERN='^(src/|resources/|config/|app/|public/|composer\.json$|composer\.lock$|composer/|package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|Dockerfile\.production$|Dockerfile\.runtime$|docker-compose\..*\.yml$|docker-compose\.yml$|docker/|vite\.config\.(js|ts)$|tsconfig\.json$|babel\.config\.js$|Makefile$|artisan$)'
while IFS= read -r FILE; do while IFS= read -r FILE; do
[ -z "$FILE" ] && continue [ -z "$FILE" ] && continue
if echo "$FILE" | grep -Eq "$RUNTIME_PATTERN"; then if echo "$FILE" | grep -Eq "$RUNTIME_PATTERN"; then
@@ -817,10 +817,8 @@ jobs:
# Job 3: Auto-deploy to Staging (only for staging branch) # Job 3: Auto-deploy to Staging (only for staging branch)
deploy-staging: deploy-staging:
name: Auto-deploy to Staging name: Auto-deploy to Staging
needs: [changes, build, runtime-base] needs: [changes]
if: | if: github.ref_name == 'staging' || github.head_ref == 'staging' || (github.ref_name == '' && contains(github.ref, 'staging'))
(github.ref_name == 'staging' || github.head_ref == 'staging' || (github.ref_name == '' && contains(github.ref, 'staging'))) &&
(needs.build.result != 'failure')
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: environment:
name: staging name: staging
@@ -876,28 +874,12 @@ jobs:
DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}" DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}"
REGISTRY_HOST="${{ env.REGISTRY }}" REGISTRY_HOST="${{ env.REGISTRY }}"
IMAGE_NAME="${{ env.IMAGE_NAME }}" IMAGE_NAME="${{ env.IMAGE_NAME }}"
BUILD_RESULT="${{ needs.build.result }}"
IMAGE_TAG_RAW="${{ needs.build.outputs.image_tag }}"
IMAGE_URL_RAW="${{ needs.build.outputs.image_url }}"
DEFAULT_IMAGE="${REGISTRY_HOST}/${IMAGE_NAME}:latest" DEFAULT_IMAGE="${REGISTRY_HOST}/${IMAGE_NAME}:latest"
SELECTED_IMAGE=""
if [ "$BUILD_RESULT" = "success" ] && [ -n "$IMAGE_URL_RAW" ] && [ "$IMAGE_URL_RAW" != "null" ]; then # Always use latest image - if a build happened, it would have pushed to latest anyway
SELECTED_IMAGE="$IMAGE_URL_RAW" # Using latest ensures we always get the most recent image, whether it was just built or not
fi
if [ -z "$SELECTED_IMAGE" ]; then
if [ "$BUILD_RESULT" = "success" ] && [ -n "$IMAGE_TAG_RAW" ] && [ "$IMAGE_TAG_RAW" != "null" ]; then
SELECTED_IMAGE="${REGISTRY_HOST}/${IMAGE_NAME}:${IMAGE_TAG_RAW}"
else
SELECTED_IMAGE="$DEFAULT_IMAGE" SELECTED_IMAGE="$DEFAULT_IMAGE"
fi
fi
if [ -z "$SELECTED_IMAGE" ]; then
SELECTED_IMAGE="$DEFAULT_IMAGE"
fi
STACK_PATH_DISPLAY="~/deployment/stacks/staging" STACK_PATH_DISPLAY="~/deployment/stacks/staging"
@@ -1024,14 +1006,9 @@ jobs:
echo "⏳ Waiting for services to start..." echo "⏳ Waiting for services to start..."
sleep 15 sleep 15
# Pull latest code from Git repository only if image was actually rebuilt # Pull latest code from Git repository - always sync code when deploying
# Skip if build was skipped (no changes detected) - container already has latest code echo "🔄 Pulling latest code from Git repository in staging-app container..."
if [ "${{ needs.build.result }}" = "success" ] && [ -n "${{ needs.build.outputs.image_url }}" ] && [ "${{ needs.build.outputs.image_url }}" != "null" ]; then
echo "🔄 Pulling latest code from Git repository in staging-app container (image was rebuilt)..."
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-app bash -c "cd /var/www/html && git -c safe.directory=/var/www/html fetch origin staging && git -c safe.directory=/var/www/html reset --hard origin/staging && git -c safe.directory=/var/www/html clean -fd" || echo "⚠️ Git pull failed, container will sync on next restart" docker compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-app bash -c "cd /var/www/html && git -c safe.directory=/var/www/html fetch origin staging && git -c safe.directory=/var/www/html reset --hard origin/staging && git -c safe.directory=/var/www/html clean -fd" || echo "⚠️ Git pull failed, container will sync on next restart"
else
echo " Skipping Git pull - no new image built, container already has latest code"
fi
# Also trigger a restart to ensure entrypoint script runs # Also trigger a restart to ensure entrypoint script runs
echo "🔄 Restarting staging-app to ensure all services are up-to-date..." echo "🔄 Restarting staging-app to ensure all services are up-to-date..."

View File

@@ -0,0 +1,201 @@
# ?? Quick Start: Semaphore mit Repository verbinden
Kurze Schritt-f?r-Schritt-Anleitung, um Semaphore mit deinem Git-Repository zu verbinden.
## Schritt 1: Semaphore starten
```bash
cd deployment/stacks/semaphore
cp env.example .env
```
**WICHTIG**: Generiere einen Encryption Key:
```bash
head -c32 /dev/urandom | base64
```
Kopiere den Key und setze ihn in `.env`:
```env
SEMAPHORE_ACCESS_KEY_ENCRYPTION=<dein-generierter-key>
```
```bash
docker compose up -d
```
## Schritt 2: Semaphore ?ffnen
?ffne im Browser: **http://localhost:9300**
**Login**:
- Username: `admin`
- Password: `admin`
## Schritt 3: Projekt erstellen
1. Klicke auf **"New Project"** oder **"Create Project"**
2. Name: **"michaelschiemer"**
3. Klicke auf **"Create"**
## Schritt 4: Inventory erstellen (Hosts definieren)
### 4.1 Inventory anlegen
1. Gehe zu **Inventories** ? **New Inventory**
2. Name: **"Production Hosts"**
3. Klicke auf **"Create"**
### 4.2 Host hinzuf?gen
1. Klicke auf dein Inventory ? **"Add Host"**
2. F?lle aus:
```
Name: production
Address: 94.16.110.151
SSH Username: deploy
SSH Port: 22
```
3. Klicke auf **"Save"**
### 4.3 SSH-Key hinzuf?gen
1. Gehe zu **Keys** (in der Seitenleiste)
2. Klicke auf **"New Key"**
3. Name: **"Deployment Key"**
4. F?ge deinen SSH Private Key ein (aus `~/.ssh/production`):
```bash
cat ~/.ssh/production
```
5. Klicke auf **"Save"**
6. Gehe zur?ck zu deinem Inventory und w?hle den Key bei deinem Host aus
## Schritt 5: Template erstellen (Playbook verwenden)
### 5.1 Template f?r CI Tests
1. Gehe zu **Templates** ? **New Template**
2. **Name**: "Run CI Tests"
3. **Inventory**: W?hle "Production Hosts"
4. **Playbook Path**: `/tmp/semaphore/playbooks/ci-tests.yml`
5. **Variables** (klicke auf "Variables"):
```yaml
repo_url: https://git.michaelschiemer.de/michael/michaelschiemer.git
repo_branch: main
```
6. Klicke auf **"Save"**
### 5.2 Template f?r Docker Build
1. **Templates** ? **New Template**
2. **Name**: "Build Docker Image"
3. **Inventory**: "Production Hosts"
4. **Playbook Path**: `/tmp/semaphore/playbooks/docker-build.yml`
5. **Variables**:
```yaml
repo_url: https://git.michaelschiemer.de/michael/michaelschiemer.git
repo_branch: main
registry_url: registry.michaelschiemer.de
registry_user: admin
registry_password: <dein-registry-passwort>
image_name: framework
image_tag: latest
```
6. **Save**
### 5.3 Template f?r Staging Deployment
1. **Templates** ? **New Template**
2. **Name**: "Deploy to Staging"
3. **Inventory**: "Production Hosts"
4. **Playbook Path**: `/tmp/semaphore/playbooks/deploy-staging.yml`
5. **Variables**:
```yaml
registry_url: registry.michaelschiemer.de
registry_user: admin
registry_password: <dein-registry-passwort>
image_name: framework
image_tag: latest
```
6. **Save**
### 5.4 Template f?r Production Deployment
1. **Templates** ? **New Template**
2. **Name**: "Deploy to Production"
3. **Inventory**: "Production Hosts"
4. **Playbook Path**: `/tmp/semaphore/playbooks/deploy-production.yml`
5. **Variables**:
```yaml
registry_url: registry.michaelschiemer.de
registry_user: admin
registry_password: <dein-registry-passwort>
image_name: framework
image_tag: latest
```
6. **Save**
## Schritt 6: Task ausf?hren
1. Gehe zu **Templates**
2. Klicke auf ein Template (z.B. "Run CI Tests")
3. Klicke auf **"Run"**
4. Beobachte die Ausf?hrung in Echtzeit
## ? Fertig!
Semaphore ist jetzt mit deinem Repository verbunden! Du kannst:
- ? CI/CD-Tasks manuell starten
- ? Playbooks aus dem Repository verwenden
- ? Deployments auf deine Server ausf?hren
## ?? Verf?gbare Playbooks
Die folgenden Playbooks sind bereits im Repository und k?nnen in Semaphore verwendet werden:
1. **`/tmp/semaphore/playbooks/ci-tests.yml`** - PHP Tests & Quality Checks
2. **`/tmp/semaphore/playbooks/docker-build.yml`** - Docker Image Build & Push
3. **`/tmp/semaphore/playbooks/deploy-staging.yml`** - Staging Deployment
4. **`/tmp/semaphore/playbooks/deploy-production.yml`** - Production Deployment
## ?? Vorhandene Ansible-Playbooks
Die vorhandenen Ansible-Playbooks aus `deployment/ansible/playbooks/` sind auch verf?gbar unter:
- `/tmp/semaphore/repo-playbooks/`
Zum Beispiel:
- `/tmp/semaphore/repo-playbooks/deploy-update.yml`
- `/tmp/semaphore/repo-playbooks/rollback.yml`
## ?? Tipps
### Docker Socket f?r Build-Tasks
Falls Docker-Build-Tasks ausgef?hrt werden sollen, f?ge den Docker Socket hinzu:
In `docker-compose.yml` bei Semaphore-Service:
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
```
Dann Semaphore neu starten:
```bash
docker compose restart semaphore
```
### Git-Integration
Die Playbooks k?nnen direkt auf das Git-Repository zugreifen (HTTPS). F?r private Repositories oder SSH-Zugriff:
1. Erstelle einen SSH-Key f?r Git in Semaphore (Keys ? New Key)
2. F?ge den Public Key zu deinem Git-Repository hinzu
3. Verwende SSH-URL in Playbook-Variablen:
```yaml
repo_url: git@git.michaelschiemer.de:michael/michaelschiemer.git
```
---
**Hilfe**: Weitere Details in `SETUP_REPOSITORY.md` und `README.md`

View File

@@ -6,9 +6,9 @@ Selbst-gehostete Semaphore CI/CD-Plattform für lokale Entwicklung, die es ermö
**Features**: **Features**:
- **Selbst-gehostet**: Läuft vollständig lokal auf dem Entwicklungsrechner - **Selbst-gehostet**: Läuft vollständig lokal auf dem Entwicklungsrechner
- **Isoliert**: Keine externen Zugriffe, nur localhost (127.0.0.1) - **Lokal isoliert**: NUR über localhost (127.0.0.1) erreichbar - KEIN externer Zugriff aus Sicherheitsgründen!
- **MySQL-Backend**: Persistente Datenbank für Projekte, Tasks und Templates - **MySQL-Backend**: Persistente Datenbank für Projekte, Tasks und Templates
- **Web-UI**: Intuitive Benutzeroberfläche für Pipeline-Management - **Web-UI**: Intuitive Benutzeroberfläche für Pipeline-Management (nur lokal)
- **Ansible-Integration**: Native Unterstützung für Ansible-Playbooks - **Ansible-Integration**: Native Unterstützung für Ansible-Playbooks
- **Docker-basiert**: Einfaches Setup und Wartung - **Docker-basiert**: Einfaches Setup und Wartung
@@ -26,7 +26,7 @@ Selbst-gehostete Semaphore CI/CD-Plattform für lokale Entwicklung, die es ermö
## Voraussetzungen ## Voraussetzungen
- Docker und Docker Compose installiert - Docker und Docker Compose installiert
- Port 3001 auf localhost frei verfügbar (3000 wird von Gitea verwendet) - Port 9300 auf localhost frei verfügbar (3000 wird von Gitea verwendet)
- Ausreichend Speicherplatz für Docker Volumes (~500MB initial) - Ausreichend Speicherplatz für Docker Volumes (~500MB initial)
## Verzeichnisstruktur ## Verzeichnisstruktur
@@ -75,7 +75,9 @@ docker compose up -d
### 4. Semaphore Web-UI öffnen ### 4. Semaphore Web-UI öffnen
Öffne im Browser: http://localhost:3001 Öffne im Browser: **http://localhost:9300**
⚠️ **WICHTIG**: Semaphore ist NUR lokal zugänglich (127.0.0.1). Es gibt KEINEN externen Zugriff aus Sicherheitsgründen.
**Standard-Login**: **Standard-Login**:
- **Username**: `admin` (oder Wert aus `SEMAPHORE_ADMIN`) - **Username**: `admin` (oder Wert aus `SEMAPHORE_ADMIN`)
@@ -104,8 +106,8 @@ MYSQL_PASSWORD=semaphore
#### Semaphore-Konfiguration #### Semaphore-Konfiguration
```env ```env
# Port-Binding (Standard: 3001) # Port-Binding (Standard: 9300)
SEMAPHORE_PORT=3001 SEMAPHORE_PORT=9300
# Admin-Benutzer # Admin-Benutzer
SEMAPHORE_ADMIN=admin SEMAPHORE_ADMIN=admin
@@ -203,7 +205,7 @@ docker compose up -d
### 1. Projekt erstellen ### 1. Projekt erstellen
1. Öffne http://localhost:3001 im Browser 1. Öffne http://localhost:9300 im Browser
2. Melde dich mit Admin-Credentials an 2. Melde dich mit Admin-Credentials an
3. Klicke auf "New Project" 3. Klicke auf "New Project"
4. Gib einen Projektnamen ein (z.B. "My Project") 4. Gib einen Projektnamen ein (z.B. "My Project")
@@ -289,9 +291,9 @@ Semaphore kann Docker-Images aus der lokalen Registry verwenden:
## Troubleshooting ## Troubleshooting
### Port-Konflikt (Port 3000 vs 3001) ### Port-Konflikt (Port 3000 vs 9300)
**Problem**: Port 3000 ist standardmäßig von Gitea belegt, daher verwendet Semaphore Port 3001. **Problem**: Port 3000 ist standardmäßig von Gitea belegt, daher verwendet Semaphore Port 9300.
**Lösung**: Wenn du einen anderen Port verwenden möchtest, setze `SEMAPHORE_PORT` in der `.env` Datei: **Lösung**: Wenn du einen anderen Port verwenden möchtest, setze `SEMAPHORE_PORT` in der `.env` Datei:
```env ```env
@@ -309,7 +311,7 @@ docker compose logs semaphore
**Häufige Ursachen**: **Häufige Ursachen**:
- MySQL ist noch nicht bereit (warte auf Health-Check) - MySQL ist noch nicht bereit (warte auf Health-Check)
- Port 3001 ist bereits belegt: `netstat -tuln | grep 3001` (oder auf Windows: `netstat -ano | findstr :3001`) - Port 9300 ist bereits belegt: `netstat -tuln | grep 9300` (oder auf Windows: `netstat -ano | findstr :9300`)
- Falsche Datenbank-Credentials - Falsche Datenbank-Credentials
**Lösung**: **Lösung**:
@@ -382,26 +384,44 @@ docker compose exec semaphore wget --no-verbose --spider http://localhost:3000/a
## Sicherheit ## Sicherheit
### Lokale Entwicklung (Aktuell) ### 🔒 Lokaler Zugriff (Nur localhost)
- ✅ Nur localhost-Zugriff (127.0.0.1:3000) Semaphore ist absichtlich NUR lokal zugänglich aus Sicherheitsgründen:
- ✅ Isoliertes Netzwerk (kein externer Zugriff)
- ✅ Keine Traefik-Integration
- ⚠️ Standard-Passwörter (nur für lokale Entwicklung)
### Für Produktion - ✅ **Nur localhost-Zugriff**: Port 9300 gebunden an 127.0.0.1 (nicht 0.0.0.0)
- ✅ **Keine externen Netzwerke**: Kein externer Zugriff möglich
- ✅ **Keine Traefik-Integration**: Keine öffentliche Route konfiguriert
- ✅ **Isoliertes Netzwerk**: Nur internes Docker-Netzwerk
- ⚠️ **Standard-Passwörter**: Nur für lokale Entwicklung - ändern bei Bedarf
Wenn du Semaphore später für Produktion nutzen willst: ### ⚠️ KEINE Produktions-Nutzung über Internet!
1. **Starke Passwörter**: Ändere alle Passwörter in `.env` **Semaphore sollte NICHT öffentlich zugänglich gemacht werden!**
Gründe:
1. **Sicherheitsrisiko**: Semaphore hat Zugriff auf SSH-Keys und Deployment-Credentials
2. **Privilegierter Zugriff**: Kann auf Produktions-Server zugreifen
3. **Keine Multi-Factor-Authentication**: Standardmäßig keine 2FA
4. **Exploits**: Selbst-gehostete Software kann Sicherheitslücken haben
### Für lokale Entwicklung (Empfohlen)
Wenn du Semaphore lokal nutzen willst:
1. **Starke Passwörter**: Ändere alle Passwörter in `.env`:
```env
SEMAPHORE_ADMIN_PASSWORD=<starkes-passwort>
MYSQL_PASSWORD=<starkes-passwort>
MYSQL_ROOT_PASSWORD=<starkes-passwort>
```
2. **Encryption Key**: Generiere einen sicheren Key: 2. **Encryption Key**: Generiere einen sicheren Key:
```bash ```bash
head -c32 /dev/urandom | base64 head -c32 /dev/urandom | base64
``` ```
3. **Traefik-Integration**: Füge Traefik-Labels für HTTPS hinzu Setze in `.env`: `SEMAPHORE_ACCESS_KEY_ENCRYPTION=<generierter-key>`
4. **LDAP/SSO**: Konfiguriere externe Authentifizierung 3. **SSH-Keys**: Stelle sicher, dass SSH-Keys sicher gespeichert sind
5. **Backup-Strategie**: Regelmäßige MySQL-Backups einrichten 4. **Backup-Strategie**: Regelmäßige MySQL-Backups einrichten
6. **Resource Limits**: Füge Memory/CPU-Limits hinzu 5. **Resource Limits**: Füge Memory/CPU-Limits hinzu (optional)
## Wartung ## Wartung
@@ -537,7 +557,7 @@ docker compose logs --tail=100
docker compose ps docker compose ps
# Semaphore-Health # Semaphore-Health
curl http://localhost:3001/api/health curl http://localhost:9300/api/health
# MySQL-Health # MySQL-Health
docker compose exec mysql mysqladmin ping -h localhost -u root -psemaphore_root docker compose exec mysql mysqladmin ping -h localhost -u root -psemaphore_root
@@ -550,7 +570,7 @@ docker compose exec mysql mysqladmin ping -h localhost -u root -psemaphore_root
**Nächste Schritte**: **Nächste Schritte**:
1. `cp env.example .env` ausführen 1. `cp env.example .env` ausführen
2. `docker compose up -d` starten 2. `docker compose up -d` starten
3. http://localhost:3001 öffnen 3. http://localhost:9300 öffnen
4. Mit Admin-Credentials anmelden 4. Mit Admin-Credentials anmelden
5. Erstes Projekt und Template erstellen 5. Erstes Projekt und Template erstellen

View File

@@ -0,0 +1,355 @@
# Git-Repository in Semaphore Self-Hosted integrieren
Diese Anleitung beschreibt, wie du dein Git-Repository in Semaphore Self-Hosted (Ansible-UI) integrierst.
## ?? ?bersicht
Semaphore Self-Hosted ist eine Web-UI f?r Ansible, die es erm?glicht:
- Ansible-Playbooks ?ber eine grafische Oberfl?che auszuf?hren
- CI/CD-Workflows mit Ansible zu automatisieren
- Git-Repositories als Playbook-Quellen zu verwenden
## ?? Schritt 1: Semaphore starten
### 1.1 Environment-Datei erstellen
```bash
cd deployment/stacks/semaphore
cp env.example .env
```
### 1.2 Encryption Key generieren (WICHTIG!)
```bash
# Linux/WSL
head -c32 /dev/urandom | base64
# Windows PowerShell
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_}) | ConvertTo-Base64
```
Kopiere den generierten Key und setze ihn in `.env`:
```env
SEMAPHORE_ACCESS_KEY_ENCRYPTION=<dein-generierter-key>
```
### 1.3 Stack starten
```bash
docker compose up -d
```
### 1.4 Web-UI ?ffnen
?ffne im Browser: **http://localhost:9300**
?? **WICHTIG**: Semaphore ist NUR lokal zug?nglich (127.0.0.1). Es gibt KEINEN externen Zugriff aus Sicherheitsgr?nden!
**Standard-Login**:
- Username: `admin`
- Password: `admin`
## ?? Schritt 2: Projekt in Semaphore erstellen
### 2.1 Neues Projekt anlegen
1. Melde dich in Semaphore an
2. Klicke auf **"New Project"** oder **"Create Project"**
3. Gib einen Projektnamen ein: **"michaelschiemer"**
4. Klicke auf **"Create"**
### 2.2 Git-Repository als Playbook-Quelle hinzuf?gen
Semaphore Self-Hosted kann Playbooks direkt aus Git-Repositories laden. Du hast zwei Optionen:
**Option A: Playbooks aus Repository-Clone verwenden** (Empfohlen)
1. Clone dein Repository lokal:
```bash
git clone https://git.michaelschiemer.de/michael/michaelschiemer.git
cd michaelschiemer
```
2. Erstelle ein Playbook-Verzeichnis f?r Semaphore:
```bash
mkdir -p deployment/stacks/semaphore/playbooks
cp deployment/ansible/playbooks/*.yml deployment/stacks/semaphore/playbooks/
```
**Option B: Playbooks direkt in Semaphore einf?gen**
Semaphore kann auch Playbooks direkt im Web-UI erstellen/bearbeiten. F?r Git-Integration ist Option A besser.
## ??? Schritt 3: Inventory erstellen
Ein Inventory definiert die Hosts, auf denen Playbooks ausgef?hrt werden.
### 3.1 Inventory anlegen
1. Gehe zu deinem Projekt ? **Inventories** ? **New Inventory**
2. Name: **"Production Hosts"** oder **"Local Hosts"**
3. Klicke auf **"Create"**
### 3.2 Host hinzuf?gen
1. Klicke auf dein Inventory ? **Add Host**
2. F?lle folgende Felder aus:
**F?r Production-Deployment:**
```
Name: production
Address: 94.16.110.151
SSH Username: deploy
SSH Port: 22
```
**F?r Staging-Deployment:**
```
Name: staging
Address: 94.16.110.151 # oder dein Staging-Host
SSH Username: deploy
SSH Port: 22
```
### 3.3 SSH-Key hinzuf?gen
1. Gehe zu **Keys** (Seitenleiste)
2. Klicke auf **"New Key"**
3. Gib einen Namen ein: **"Deployment Key"**
4. F?ge deinen SSH Private Key ein (aus `~/.ssh/production` oder ?hnlich)
5. W?hle den Key im Host-Inventory aus
## ?? Schritt 4: Template erstellen
Templates verbinden Playbooks mit Inventories und definieren Parameter.
### 4.1 Template f?r Tests erstellen
1. Gehe zu **Templates** ? **New Template**
2. Template-Name: **"Run PHP Tests"**
3. Inventory: W?hle dein Inventory
4. Playbook: Erstelle ein Playbook oder verwende ein vorhandenes:
```yaml
---
- name: Run PHP Tests
hosts: localhost
gather_facts: no
tasks:
- name: Checkout repository
git:
repo: https://git.michaelschiemer.de/michael/michaelschiemer.git
dest: /tmp/ci-build
version: main
- name: Install dependencies
command: composer install --no-interaction --prefer-dist
args:
chdir: /tmp/ci-build
- name: Run tests
command: ./vendor/bin/pest
args:
chdir: /tmp/ci-build
register: test_result
- name: Show test results
debug:
var: test_result.stdout_lines
```
5. Speichere das Template
### 4.2 Template f?r Build erstellen
Erstelle ein Template f?r Docker Image Build:
```yaml
---
- name: Build and Push Docker Image
hosts: localhost
gather_facts: no
tasks:
- name: Checkout repository
git:
repo: https://git.michaelschiemer.de/michael/michaelschiemer.git
dest: /tmp/ci-build
version: main
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
vars:
registry_user: "admin"
registry_url: "registry.michaelschiemer.de"
- name: Build Docker image
docker_image:
name: "{{ registry_url }}/framework:{{ image_tag }}"
tag: "{{ image_tag }}"
source: build
build:
path: /tmp/ci-build
dockerfile: Dockerfile.production
push: yes
vars:
registry_url: "registry.michaelschiemer.de"
image_tag: "latest"
```
### 4.3 Template f?r Deployment erstellen
Erstelle ein Template f?r Production-Deployment (verwendet die vorhandenen Ansible-Playbooks):
```yaml
---
- name: Deploy to Production
hosts: production
gather_facts: yes
become: yes
tasks:
- name: Checkout deployment scripts
git:
repo: https://git.michaelschiemer.de/michael/michaelschiemer.git
dest: /tmp/deployment
version: main
- name: Include deployment playbook
include_role:
name: deployment/ansible/playbooks/deploy-update.yml
```
Oder verwende die vorhandenen Ansible-Playbooks direkt:
**Vorteil**: Die vorhandenen Playbooks in `deployment/ansible/playbooks/` k?nnen direkt verwendet werden!
1. Erstelle ein Template: **"Deploy to Production"**
2. W?hle Inventory: **"Production Hosts"**
3. Playbook-Pfad: **`deployment/ansible/playbooks/deploy-update.yml`**
4. Speichere
## ?? Schritt 5: CI/CD-Workflow einrichten
### 5.1 Git-Webhook konfigurieren (Optional)
Semaphore kann Webhooks von Git-Repositories empfangen, um automatisch Tasks zu starten.
1. Gehe zu deinem Git-Repository (Gitea)
2. Settings ? Webhooks ? Add Webhook
3. Webhook-URL: `http://localhost:9300/api/hook/git` (nur lokal!)
4. Content-Type: `application/json`
5. Secret: Optional, aber empfohlen
### 5.2 Task manuell starten
1. Gehe zu **Templates**
2. Klicke auf dein Template (z.B. "Run PHP Tests")
3. Klicke auf **"Run"**
4. Beobachte die Ausf?hrung in Echtzeit
### 5.3 Task automatisch starten
Semaphore kann Tasks basierend auf Git-Events starten:
1. Gehe zu Template ? **Settings**
2. Aktiviere **"Auto Run on Push"**
3. W?hle Branch: `main` oder `staging`
4. Speichere
## ?? Verwendung vorhandener Ansible-Playbooks
### Vorhandene Playbooks verwenden
Die vorhandenen Ansible-Playbooks in `deployment/ansible/playbooks/` k?nnen direkt in Semaphore verwendet werden:
1. **Mounte Playbooks als Volume** (in `docker-compose.yml`):
```yaml
volumes:
- ./../../ansible/playbooks:/tmp/semaphore/playbooks:ro
```
2. **Erstelle Templates**, die auf diese Playbooks verweisen:
- Playbook-Pfad: `/tmp/semaphore/playbooks/deploy-update.yml`
- Inventory: W?hle dein Production-Inventory
### Beispiel-Templates
#### Template: Deploy Update
- Name: "Deploy Update"
- Inventory: "Production Hosts"
- Playbook: `/tmp/semaphore/playbooks/deploy-update.yml`
- Variables:
```yaml
registry_url: registry.michaelschiemer.de
image_name: framework
image_tag: latest
```
#### Template: Rollback
- Name: "Rollback"
- Inventory: "Production Hosts"
- Playbook: `/tmp/semaphore/playbooks/rollback.yml`
- Variables:
```yaml
registry_url: registry.michaelschiemer.de
image_name: framework
```
## ?? Erweiterte Konfiguration
### Docker Socket f?r Build-Tasks
F?r Docker-Build-Tasks muss der Semaphore-Container Zugriff auf den Docker-Socket haben:
**In `docker-compose.yml` hinzuf?gen:**
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
```
### Environment-Variablen f?r Templates
Du kannst Environment-Variablen in Templates verwenden:
1. Gehe zu Template ? **Variables**
2. F?ge Variablen hinzu:
- `registry_url`: `registry.michaelschiemer.de`
- `registry_user`: `admin`
- `image_name`: `framework`
### Git-Integration mit SSH-Keys
F?r private Repositories:
1. Gehe zu **Keys**
2. Erstelle einen SSH-Key f?r Git-Access
3. F?ge den Public Key zu deinem Git-Repository hinzu (Deploy Keys)
4. Verwende SSH-URL in Git-Tasks:
```yaml
git:
repo: git@git.michaelschiemer.de:michael/michaelschiemer.git
```
## ?? N?tzliche Links
- [Semaphore Self-Hosted Dokumentation](https://docs.ansible-semaphore.com/)
- [Ansible Semaphore GitHub](https://github.com/ansible-semaphore/semaphore)
- [Ansible Playbook Dokumentation](https://docs.ansible.com/ansible/latest/playbook_guide/index.html)
## ? Checkliste
- [ ] Semaphore gestartet (http://localhost:9300)
- [ ] Projekt erstellt
- [ ] Inventory mit Hosts erstellt
- [ ] SSH-Keys f?r Host-Zugriff konfiguriert
- [ ] Template f?r Tests erstellt
- [ ] Template f?r Build erstellt
- [ ] Template f?r Deployment erstellt
- [ ] Erstes Template erfolgreich ausgef?hrt
- [ ] Git-Webhook konfiguriert (optional)
---
**N?chste Schritte**: Starte Semaphore und erstelle dein erstes Template!

View File

@@ -38,24 +38,12 @@ services:
networks: networks:
- semaphore-internal - semaphore-internal
ports: ports:
# Only bind to localhost, not external interfaces # ONLY bind to localhost (127.0.0.1) - NOT accessible externally!
# Default port 3001 to avoid conflict with Gitea (port 3000) # Default port 9300 to avoid conflict with Gitea (port 3000)
- "127.0.0.1:${SEMAPHORE_PORT:-3001}:3000" # SECURITY: This ensures Semaphore is only accessible locally
labels: - "127.0.0.1:${SEMAPHORE_PORT:-9300}:3000"
# Traefik configuration # NO Traefik labels - Semaphore should only be accessible locally!
- "traefik.enable=true" # External access is disabled for security reasons.
# HTTP Router (redirects to HTTPS)
- "traefik.http.routers.semaphore.rule=Host(`semaphore.michaelschiemer.de`)"
- "traefik.http.routers.semaphore.entrypoints=web"
- "traefik.http.routers.semaphore.middlewares=redirect-to-https"
# HTTPS Router
- "traefik.http.routers.semaphore-secure.rule=Host(`semaphore.michaelschiemer.de`)"
- "traefik.http.routers.semaphore-secure.entrypoints=websecure"
- "traefik.http.routers.semaphore-secure.tls=true"
- "traefik.http.routers.semaphore-secure.tls.certresolver=letsencrypt"
- "traefik.http.routers.semaphore-secure.service=semaphore"
# Service definition (use localhost port binding)
# Note: Dynamic config in traefik/dynamic/semaphore.yml takes precedence
environment: environment:
- TZ=Europe/Berlin - TZ=Europe/Berlin
# Database Configuration # Database Configuration
@@ -80,6 +68,9 @@ services:
- SEMAPHORE_WEBHOOK_URL=${SEMAPHORE_WEBHOOK_URL:-} - SEMAPHORE_WEBHOOK_URL=${SEMAPHORE_WEBHOOK_URL:-}
volumes: volumes:
- semaphore-data:/etc/semaphore - semaphore-data:/etc/semaphore
# Mount playbooks from repository so Semaphore can access them
- ../../../deployment/stacks/semaphore/playbooks:/tmp/semaphore/playbooks:ro
- ../../../deployment/ansible/playbooks:/tmp/semaphore/repo-playbooks:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
healthcheck: healthcheck:

View File

@@ -14,10 +14,10 @@ MYSQL_PASSWORD=semaphore
# Semaphore Configuration # Semaphore Configuration
# ============================================ # ============================================
# Port binding (default: 3001) # Port binding (default: 9300)
# Only accessible via localhost (127.0.0.1) # Only accessible via localhost (127.0.0.1)
# Note: Changed from 3000 to avoid conflict with Gitea # Note: Changed from 3000 to avoid conflict with Gitea
SEMAPHORE_PORT=3001 SEMAPHORE_PORT=9300
# Admin User Configuration # Admin User Configuration
SEMAPHORE_ADMIN=admin SEMAPHORE_ADMIN=admin

View File

@@ -0,0 +1,103 @@
---
# CI Tests Playbook f?r Semaphore
# F?hrt PHP Tests und Quality Checks aus
- name: Run CI Tests and Quality Checks
hosts: localhost
gather_facts: no
vars:
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
repo_branch: "{{ repo_branch | default('main') }}"
build_dir: "/tmp/ci-build"
tasks:
- name: Clean up build directory
file:
path: "{{ build_dir }}"
state: absent
- name: Checkout repository
git:
repo: "{{ repo_url }}"
dest: "{{ build_dir }}"
version: "{{ repo_branch }}"
force: yes
register: git_result
- name: Display checked out commit
debug:
msg: "Checked out commit: {{ git_result.after }}"
- name: Install Composer if not present
get_url:
url: https://getcomposer.org/installer
dest: /tmp/composer-installer.php
mode: '0755'
when: ansible_facts.os_family == "Debian"
- name: Install Composer (Debian)
shell: php /tmp/composer-installer.php && mv composer.phar /usr/local/bin/composer
when: ansible_facts.os_family == "Debian"
args:
creates: /usr/local/bin/composer
- name: Install PHP dependencies
command: composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=php
args:
chdir: "{{ build_dir }}"
register: composer_result
- name: Display composer output
debug:
var: composer_result.stdout_lines
when: composer_result.stdout_lines is defined
- name: Run PHP tests
command: ./vendor/bin/pest
args:
chdir: "{{ build_dir }}"
register: test_result
ignore_errors: yes
- name: Display test results
debug:
msg: "{{ test_result.stdout_lines }}"
when: test_result.stdout_lines is defined
- name: Run PHPStan
command: composer phpstan
args:
chdir: "{{ build_dir }}"
register: phpstan_result
ignore_errors: yes
when: test_result.rc == 0
- name: Display PHPStan results
debug:
msg: "{{ phpstan_result.stdout_lines }}"
when: phpstan_result.stdout_lines is defined and phpstan_result.rc == 0
- name: Run code style check
command: composer cs
args:
chdir: "{{ build_dir }}"
register: cs_result
ignore_errors: yes
when: test_result.rc == 0
- name: Display code style results
debug:
msg: "{{ cs_result.stdout_lines }}"
when: cs_result.stdout_lines is defined
- name: Fail if tests failed
fail:
msg: "Tests failed! Check output above."
when: test_result.rc != 0
- name: Summary
debug:
msg:
- "? CI Tests completed successfully!"
- "Commit: {{ git_result.after }}"
- "Branch: {{ repo_branch }}"

View File

@@ -0,0 +1,129 @@
---
# Production Deployment Playbook f?r Semaphore
# Deployed die Anwendung auf Production-Server
- name: Deploy to Production
hosts: production
gather_facts: yes
become: yes
vars:
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
image_name: "{{ image_name | default('framework') }}"
image_tag: "{{ image_tag | default('latest') }}"
registry_user: "{{ registry_user | default('admin') }}"
registry_password: "{{ registry_password | required }}"
deployment_path: "{{ deployment_path | default('~/deployment/stacks/application') }}"
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
tasks:
- name: Display deployment info
debug:
msg:
- "?? Starting production deployment..."
- "Host: {{ inventory_hostname }}"
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}:{{ image_tag }}"
- "Path: {{ deployment_path }}"
- name: Ensure deployment directory exists
file:
path: "{{ deployment_path }}"
state: directory
mode: '0755'
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
- name: Pull Docker image
docker_image:
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
source: pull
- name: Check if docker-compose files exist
stat:
path: "{{ deployment_path }}/docker-compose.base.yml"
register: base_compose_exists
- name: Check if docker-compose.production.yml exists
stat:
path: "{{ deployment_path }}/docker-compose.production.yml"
register: production_compose_exists
- name: Copy docker-compose files if missing
copy:
src: "{{ item.src }}"
dest: "{{ deployment_path }}/{{ item.dest }}"
mode: '0644'
loop:
- { src: "docker-compose.base.yml", dest: "docker-compose.base.yml" }
- { src: "docker-compose.production.yml", dest: "docker-compose.production.yml" }
when: not (item.dest == "docker-compose.base.yml" and base_compose_exists.stat.exists) or
not (item.dest == "docker-compose.production.yml" and production_compose_exists.stat.exists)
delegate_to: localhost
become: no
- name: Update docker-compose.production.yml with new image
replace:
path: "{{ deployment_path }}/docker-compose.production.yml"
regexp: 'image:.*{{ image_name }}:.*'
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
- name: Update docker-compose.production.yml with new image (alternative format)
replace:
path: "{{ deployment_path }}/docker-compose.production.yml"
regexp: 'image:.*{{ image_name }}@.*'
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
- name: Ensure Docker networks exist
docker_network:
name: traefik-public
state: present
ignore_errors: yes
- name: Deploy services
docker_compose_v2:
project_src: "{{ deployment_path }}"
files:
- "{{ deployment_path }}/docker-compose.base.yml"
- "{{ deployment_path }}/docker-compose.production.yml"
pull: missing
state: present
recreate: always
- name: Wait for services to start
pause:
seconds: 15
- name: Check container status
command: docker compose -f {{ deployment_path }}/docker-compose.base.yml -f {{ deployment_path }}/docker-compose.production.yml ps
register: container_status
- name: Display container status
debug:
var: container_status.stdout_lines
- name: Health check
uri:
url: https://michaelschiemer.de/health
method: GET
status_code: [200, 201, 204]
validate_certs: no
register: health_result
retries: 10
delay: 10
until: health_result.status == 200
ignore_errors: yes
- name: Display health check result
debug:
msg: "? Health check passed!" if health_result.status == 200 else "?? Health check failed"
- name: Summary
debug:
msg:
- "? Production deployment completed!"
- "Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}"
- "URL: https://michaelschiemer.de"

View File

@@ -0,0 +1,110 @@
---
# Staging Deployment Playbook f?r Semaphore
# Deployed die Anwendung auf Staging-Server
- name: Deploy to Staging
hosts: staging
gather_facts: yes
become: yes
vars:
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
image_name: "{{ image_name | default('framework') }}"
image_tag: "{{ image_tag | default('latest') }}"
registry_user: "{{ registry_user | default('admin') }}"
registry_password: "{{ registry_password | required }}"
deployment_path: "{{ deployment_path | default('~/deployment/stacks/staging') }}"
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
tasks:
- name: Display deployment info
debug:
msg:
- "?? Starting staging deployment..."
- "Host: {{ inventory_hostname }}"
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}:{{ image_tag }}"
- "Path: {{ deployment_path }}"
- name: Ensure deployment directory exists
file:
path: "{{ deployment_path }}"
state: directory
mode: '0755'
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
- name: Pull Docker image
docker_image:
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
source: pull
- name: Check if docker-compose files exist
stat:
path: "{{ deployment_path }}/docker-compose.base.yml"
register: base_compose_exists
- name: Check if docker-compose.staging.yml exists
stat:
path: "{{ deployment_path }}/docker-compose.staging.yml"
register: staging_compose_exists
- name: Copy docker-compose files if missing
copy:
src: "{{ item.src }}"
dest: "{{ deployment_path }}/{{ item.dest }}"
mode: '0644'
loop:
- { src: "docker-compose.base.yml", dest: "docker-compose.base.yml" }
- { src: "docker-compose.staging.yml", dest: "docker-compose.staging.yml" }
when: not (item.dest == "docker-compose.base.yml" and base_compose_exists.stat.exists) or
not (item.dest == "docker-compose.staging.yml" and staging_compose_exists.stat.exists)
delegate_to: localhost
become: no
- name: Update docker-compose.staging.yml with new image
replace:
path: "{{ deployment_path }}/docker-compose.staging.yml"
regexp: 'image:.*{{ image_name }}:.*'
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
- name: Ensure Docker networks exist
docker_network:
name: "{{ item }}"
state: present
loop:
- traefik-public
- staging-internal
ignore_errors: yes
- name: Deploy services
docker_compose_v2:
project_src: "{{ deployment_path }}"
files:
- "{{ deployment_path }}/docker-compose.base.yml"
- "{{ deployment_path }}/docker-compose.staging.yml"
pull: missing
state: present
recreate: always
- name: Wait for services to start
pause:
seconds: 15
- name: Check container status
command: docker compose -f {{ deployment_path }}/docker-compose.base.yml -f {{ deployment_path }}/docker-compose.staging.yml ps
register: container_status
- name: Display container status
debug:
var: container_status.stdout_lines
- name: Summary
debug:
msg:
- "? Staging deployment completed!"
- "Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}"
- "URL: https://staging.michaelschiemer.de"

View File

@@ -0,0 +1,103 @@
---
# Docker Build Playbook f?r Semaphore
# Baut Docker Image und pusht es zur Registry
- name: Build and Push Docker Image
hosts: localhost
gather_facts: no
vars:
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
repo_branch: "{{ repo_branch | default('main') }}"
build_dir: "/tmp/ci-build"
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
image_name: "{{ image_name | default('framework') }}"
image_tag: "{{ image_tag | default('latest') }}"
registry_user: "{{ registry_user | default('admin') }}"
registry_password: "{{ registry_password | required }}"
tasks:
- name: Clean up build directory
file:
path: "{{ build_dir }}"
state: absent
- name: Checkout repository
git:
repo: "{{ repo_url }}"
dest: "{{ build_dir }}"
version: "{{ repo_branch }}"
force: yes
register: git_result
- name: Get short commit SHA
shell: echo "{{ git_result.after }}" | cut -c1-7
register: short_sha
- name: Generate image tags
set_fact:
tags:
- "{{ registry_url }}/{{ image_name }}:latest"
- "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
- "{{ registry_url }}/{{ image_name }}:git-{{ short_sha.stdout }}"
- name: Display image tags
debug:
msg:
- "??? Building Docker image..."
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}"
- "Tags: {{ tags | join(', ') }}"
- name: Ensure Docker is available
command: docker --version
register: docker_version
- name: Display Docker version
debug:
msg: "Docker version: {{ docker_version.stdout }}"
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
register: login_result
- name: Verify registry login
debug:
msg: "? Successfully logged in to {{ registry_url }}"
when: login_result.failed == false
- name: Build Docker image
docker_image:
name: "{{ registry_url }}/{{ image_name }}"
tag: "{{ image_tag }}"
source: build
build:
path: "{{ build_dir }}"
dockerfile: Dockerfile.production
push: yes
state: present
register: build_result
- name: Tag image with additional tags
docker_image:
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
repository: "{{ registry_url }}/{{ image_name }}"
tag: "{{ item }}"
source: local
push: yes
state: present
loop:
- "latest"
- "git-{{ short_sha.stdout }}"
when: build_result.changed
- name: Summary
debug:
msg:
- "? Docker image built and pushed successfully!"
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}"
- "Tags: {{ tags | join(', ') }}"
- "Commit: {{ git_result.after }}"

View File

@@ -16,6 +16,63 @@ Traefik acts as the central reverse proxy for all services, handling:
- Zus?tzlich durch BasicAuth gesch?tzt - Zus?tzlich durch BasicAuth gesch?tzt
- ?ffentlicher Zugriff ist blockiert - ?ffentlicher Zugriff ist blockiert
## Local Development
For local development, use the separate local configuration to avoid port conflicts and Let's Encrypt errors:
### Quick Start (Local Development)
```bash
# Ensure Docker network exists
docker network create traefik-public 2>/dev/null || true
# Start Traefik with local configuration
cd deployment/stacks/traefik
docker compose -f docker-compose.local.yml up -d
# Check logs
docker compose -f docker-compose.local.yml logs -f
# Access dashboard at http://localhost:8080/dashboard/
```
### Local Development Configuration
The local configuration (`docker-compose.local.yml` and `traefik.local.yml`) differs from production:
- **Bridge network** instead of `host` mode (avoids port conflicts)
- **Port mappings**: `8080:80` only (HTTP-only for local development)
- Note: HTTPS not needed locally - avoids port conflicts with web container (8443:443)
- **HTTP-only** (no ACME/Let's Encrypt) for local development
- **Dashboard**: Accessible at `http://localhost:8080/dashboard/` (HTTP, no authentication)
- Also available: `http://localhost:8080/api/rawdata` and `http://localhost:8080/api/http/routers`
- **No `acme.json`** required
- **Console logging** (human-readable) instead of JSON file logs
### Local Development vs Production
| Feature | Local (`docker-compose.local.yml`) | Production (`docker-compose.yml`) |
|---------|-----------------------------------|----------------------------------|
| Network Mode | Bridge | Host |
| Ports | 8080:80 (HTTP only) | Direct binding (80, 443) |
| SSL/TLS | HTTP-only | HTTPS with Let's Encrypt |
| Dashboard | `http://localhost:8080/dashboard/` | `https://traefik.michaelschiemer.de` |
| Authentication | None (local dev) | VPN + BasicAuth |
| Logging | Console (human-readable) | JSON files |
| ACME | Disabled | Enabled |
### Troubleshooting Local Development
**Container restarts in loop:**
- Check if port 8080 is already in use: `netstat -tlnp | grep ':8080' || ss -tlnp | grep ':8080'`
- Verify Docker network exists: `docker network ls | grep traefik-public`
- Check logs: `docker compose -f docker-compose.local.yml logs -f traefik`
**Services not accessible through Traefik:**
- Ensure services are on `traefik-public` network
- Verify Traefik labels are correctly configured
- Check that services are running: `docker compose ps`
## Prerequisites ## Prerequisites
1. **Docker Network** 1. **Docker Network**

View File

@@ -0,0 +1,59 @@
# Local Development Configuration for Traefik
# Usage: docker compose -f docker-compose.local.yml up -d
#
# This configuration is optimized for local development:
# - Bridge network instead of host mode
# - Port mapping: 8080:80 (HTTP only - HTTPS not needed for local dev)
# Note: 8443:443 is used by the web container, and we don't need HTTPS for Traefik locally
# - No ACME/Let's Encrypt (HTTP-only)
# - Simplified healthcheck
services:
traefik:
image: traefik:v3.0
container_name: traefik-local
restart: unless-stopped
security_opt:
- no-new-privileges:true
# Use bridge network for local development (avoids port conflicts)
# Ports 80/443 might be in use by other services
# For local development, we only use HTTP (no HTTPS needed)
# Note: 8443:443 is used by the web container
ports:
- "8080:80" # HTTP on port 80 (mapped to host port 8080)
- "8080:8080" # Traefik API entrypoint (for api.insecure=true dashboard)
environment:
- TZ=Europe/Berlin
volumes:
# Docker socket for service discovery
- /var/run/docker.sock:/var/run/docker.sock:ro
# Static configuration for local development
- ./traefik.local.yml:/traefik.yml:ro
# Dynamic configuration (shared with production config)
# Note: These configs reference letsencrypt resolver which we don't configure locally
# This will cause harmless errors in logs but won't break functionality
- ./dynamic:/dynamic:ro
networks:
- traefik-public
labels:
# Note: With api.insecure=true, Traefik should automatically serve the dashboard
# at /dashboard/ and /api/ without needing router labels.
# However, if this doesn't work in bridge network mode, we may need explicit routing.
# For now, we'll try without labels and see if api.insecure=true works directly.
- "traefik.enable=true"
healthcheck:
# Use wget or curl to check Traefik ping endpoint
# The ping endpoint is configured in traefik.local.yml on the 'web' entrypoint
# Try ping endpoint first, if that fails, try API endpoint
test: ["CMD-SHELL", "wget --quiet --spider http://localhost:80/ping || wget --quiet --spider http://localhost:80/api/rawdata || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
networks:
traefik-public:
external: true
# Create this network if it doesn't exist:
# docker network create traefik-public

View File

@@ -0,0 +1,98 @@
# Local Development Configuration for Traefik
# This configuration is optimized for local development without Let's Encrypt/ACME
# Global Configuration
global:
checkNewVersion: true
sendAnonymousUsage: false
# API and Dashboard
# For local development, we enable insecure access on port 8080
# Dashboard automatically accessible at:
# - http://localhost:8080/dashboard/ (with trailing slash)
# - http://localhost:8080/api/rawdata
# - http://localhost:8080/api/http/routers
api:
dashboard: true
insecure: true # HTTP-only for local development - enables direct dashboard access
# Note: With insecure=true, dashboard is accessible at:
# - http://localhost:8080/dashboard/
# - http://localhost:8080/api/rawdata
# - http://localhost:8080/api/http/routers
# The insecure mode works directly on the entrypoint (web) without needing router labels
# Entry Points
entryPoints:
web:
address: ":80"
# No redirects for local development - HTTP is acceptable
websecure:
address: ":443"
# Note: Even though we don't use HTTPS locally, we need this entrypoint
# because dynamic configurations (gitea.yml, semaphore.yml) reference it
# We use HTTP only, but the entrypoint must exist to avoid errors
traefik:
address: ":8080"
# This entrypoint is used by api.insecure=true for dashboard access
# It must be on port 8080 (which maps to host port 8080) to match our port mapping
# Certificate Resolvers
# Note: For local development, we don't configure ACME/Let's Encrypt
# Dynamic configs (gitea.yml, semaphore.yml) that reference letsencrypt will show errors
# but won't break Traefik functionality. We can ignore these errors for local dev.
# If you need to test with real certificates locally, configure ACME manually.
# certificatesResolvers:
# letsencrypt:
# acme:
# email: your-email@example.com
# storage: /tmp/acme.json
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
# Providers
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
# Use Docker bridge network for local development
network: traefik-public
watch: true
file:
directory: /dynamic
watch: true
# Note: Dynamic configs (gitea.yml, semaphore.yml) will show errors
# because they reference letsencrypt resolver which we don't configure locally
# These errors are harmless and won't affect local development
# Forwarded Headers Configuration
# Simplified for local development
forwardedHeaders:
trustedIPs:
- "127.0.0.1/32" # Localhost
- "172.17.0.0/16" # Docker bridge network
- "172.18.0.0/16" # Docker user-defined networks
insecure: true # Allow insecure forwarded headers for local dev
# Logging - Console output for local development (easier to debug)
log:
level: INFO
format: common # Human-readable format for local development
# Access Logs - Console output for local development
accessLog:
format: common # Human-readable format for local development
# Metrics (optional for local development)
# Can be enabled if needed for monitoring
# metrics:
# prometheus:
# addEntryPointsLabels: true
# addRoutersLabels: true
# addServicesLabels: true
# Ping endpoint for health checks
ping:
entryPoint: web

View File

@@ -12,7 +12,7 @@ services:
container_name: web container_name: web
ports: ports:
- "8888:80" - "8888:80"
- "8443:443" - "443:443" # HTTPS auf Standard-Port 443 für direkten Zugriff via https://localhost
environment: environment:
- APP_ENV=${APP_ENV:-development} - APP_ENV=${APP_ENV:-development}
volumes: volumes: