feat(Docker): Upgrade to PHP 8.5.0RC3 with native ext-uri support

BREAKING CHANGE: Requires PHP 8.5.0RC3

Changes:
- Update Docker base image from php:8.4-fpm to php:8.5.0RC3-fpm
- Enable ext-uri for native WHATWG URL parsing support
- Update composer.json PHP requirement from ^8.4 to ^8.5
- Add ext-uri as required extension in composer.json
- Move URL classes from Url.php85/ to Url/ directory (now compatible)
- Remove temporary PHP 8.4 compatibility workarounds

Benefits:
- Native URL parsing with Uri\WhatWg\Url class
- Better performance for URL operations
- Future-proof with latest PHP features
- Eliminates PHP version compatibility issues
This commit is contained in:
2025-10-27 09:31:28 +01:00
parent 799f74f00a
commit c8b47e647d
81 changed files with 6988 additions and 601 deletions

View File

@@ -0,0 +1,239 @@
# Git-Based Deployment mit Gitea
## Übersicht
Das Git-basierte Deployment Playbook (`deploy-git-based.yml`) ermöglicht Zero-Downtime Deployments mit Gitea als Git-Repository-Server.
## Voraussetzungen
### 1. Gitea Server Setup
Der Gitea Server muss für den Production-Server erreichbar sein. Es gibt zwei Optionen:
#### Option A: Öffentlich erreichbarer Gitea Server (Empfohlen für Production)
```bash
# Gitea muss über das Internet erreichbar sein
git_repo: "git@git.michaelschiemer.de:michael/michaelschiemer.git"
```
**Erforderlich**:
- Öffentliche IP oder Domain für Gitea
- Firewall-Regel für Port 2222 (SSH)
- SSL/TLS für Webinterface (Port 9443/3000)
#### Option B: Gitea auf dem Production-Server
```bash
# Gitea läuft auf demselben Server wie die Anwendung
git_repo: "git@localhost:michael/michaelschiemer.git"
```
**Erforderlich**:
- Gitea Container auf Production-Server deployen
- Docker Compose Setup auf Production-Server
- Lokale SSH-Konfiguration
### 2. SSH Key Setup
Der Deploy-User auf dem Production-Server benötigt einen SSH-Key:
```bash
# Auf dem Production-Server
ssh-keygen -t ed25519 -C "deployment@michaelschiemer" -f ~/.ssh/gitea_deploy_key -N ""
# Public Key zu Gitea hinzufügen (via Web-UI oder API)
cat ~/.ssh/gitea_deploy_key.pub
```
### 3. SSH Keys im Secrets-Verzeichnis
Die SSH Keys müssen im `deployment/infrastructure/secrets/` Verzeichnis liegen:
```bash
deployment/infrastructure/secrets/
├── .gitignore # Schützt Keys vor versehentlichem Commit
├── gitea_deploy_key # Private Key
└── gitea_deploy_key.pub # Public Key
```
**WICHTIG**: Das `secrets/` Verzeichnis ist via `.gitignore` geschützt und darf NIEMALS committed werden!
## Deployment-Ablauf
### 1. SSH Key auf Production-Server kopieren
Das Playbook kopiert automatisch die SSH Keys aus `secrets/` auf den Production-Server:
```yaml
- name: Copy Gitea deploy SSH private key
copy:
src: "{{ playbook_dir }}/../secrets/gitea_deploy_key"
dest: "/home/{{ app_user }}/.ssh/gitea_deploy_key"
mode: '0600'
```
### 2. SSH-Konfiguration
Das Playbook erstellt automatisch die SSH-Konfiguration:
```ssh
Host localhost
HostName localhost
Port 2222
User git
IdentityFile ~/.ssh/gitea_deploy_key
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
Host git.michaelschiemer.de
HostName git.michaelschiemer.de
Port 2222
User git
IdentityFile ~/.ssh/gitea_deploy_key
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
```
### 3. Git Clone
Das Playbook clont das Repository in ein Release-Verzeichnis:
```bash
/var/www/michaelschiemer/
├── releases/
│ ├── 1761524417/ # Timestamp-basierte Releases
│ └── v1.0.0/ # Tag-basierte Releases
├── shared/ # Shared Directories (symlinked)
│ ├── storage/
│ └── .env.production
└── current -> releases/1761524417 # Symlink auf aktives Release
```
### 4. Zero-Downtime Deployment
- Neues Release wird geclont
- Dependencies installiert
- Symlinks erstellt
- `current` Symlink atomar gewechselt
- Health Check durchgeführt
- Bei Fehler: Automatischer Rollback
## Deployment ausführen
### Standard Deployment (main Branch)
```bash
cd deployment/infrastructure
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-git-based.yml
```
### Tag-basiertes Deployment
```bash
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-git-based.yml \
--extra-vars "release_tag=v1.0.0"
```
### Custom Branch Deployment
```bash
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-git-based.yml \
--extra-vars "git_branch=develop"
```
## Konfiguration anpassen
### Git Repository URL ändern
In `deploy-git-based.yml`:
```yaml
vars:
git_repo: "git@git.michaelschiemer.de:michael/michaelschiemer.git"
# Oder für lokales Testing:
# git_repo: "git@localhost:michael/michaelschiemer.git"
```
### Shared Directories anpassen
```yaml
vars:
shared_dirs:
- storage/logs
- storage/cache
- storage/sessions
- storage/uploads
- public/uploads
shared_files:
- .env.production
```
## Troubleshooting
### Fehler: "Connection refused" zu Gitea
**Problem**: Der Production-Server kann Gitea nicht erreichen.
**Lösung**:
1. Prüfe, ob Gitea öffentlich erreichbar ist: `nc -zv git.michaelschiemer.de 2222`
2. Prüfe Firewall-Regeln auf dem Gitea-Server
3. Für lokales Testing: Verwende rsync-based Deployment stattdessen
### Fehler: "Permission denied (publickey)"
**Problem**: SSH Key ist nicht korrekt konfiguriert.
**Lösung**:
1. Prüfe, ob der Public Key in Gitea hinzugefügt wurde
2. Prüfe SSH Key Permissions: `chmod 600 ~/.ssh/gitea_deploy_key`
3. Teste SSH-Verbindung manuell: `ssh -p 2222 -i ~/.ssh/gitea_deploy_key git@git.michaelschiemer.de`
### Health Check schlägt fehl
**Problem**: Deployment-Health-Check failed.
**Lösung**:
1. Automatischer Rollback wurde durchgeführt
2. Prüfe Logs: `tail -f /var/www/michaelschiemer/deploy.log`
3. Prüfe Application Logs: `/var/www/michaelschiemer/shared/storage/logs/`
## Comparison: Git-based vs rsync-based
### Git-based Deployment (Dieser Playbook)
**Vorteile**:
- Zero-Downtime durch Symlink-Switch
- Atomare Releases mit Rollback-Fähigkeit
- Git-Historie auf Production-Server
- Einfache Rollbacks zu vorherigen Releases
**Nachteile**:
- Gitea Server muss erreichbar sein
- Zusätzliche Infrastruktur (Gitea)
- SSH Key Management erforderlich
### rsync-based Deployment
**Vorteile**:
- Keine zusätzliche Infrastruktur
- Funktioniert mit lokalem Development-Environment
- Schneller für kleine Änderungen
**Nachteile**:
- Kein Zero-Downtime ohne zusätzliche Logik
- Keine Git-Historie auf Server
- Rollback komplizierter
## Empfehlung
**Für Production**: Git-based Deployment mit öffentlich erreichbarem Gitea Server
**Für Development/Testing**: rsync-based Deployment (bereits implementiert und getestet)
## Related Files
- `deploy-git-based.yml` - Git-based Deployment Playbook
- `deploy-rsync-based.yml` - rsync-based Deployment Playbook (Alternative)
- `rollback-git-based.yml` - Rollback Playbook für Git-Deployments
- `secrets/.gitignore` - Schutz für SSH Keys

View File

@@ -0,0 +1,652 @@
# Rsync-Based Deployment
**Production-ready Zero-Downtime Deployment** mit Rsync, Release Management und automatischem Rollback.
## Übersicht
Das Rsync-basierte Deployment Playbook (`deploy-rsync-based.yml`) bietet eine robuste Lösung für Production Deployments ohne externe Git-Server-Abhängigkeiten.
**Vorteile**:
- ✅ Zero-Downtime durch Symlink-Switch
- ✅ Automatischer Rollback bei Health Check Failure
- ✅ Git Tag-basiertes Release Management
- ✅ Keine Gitea/GitHub Abhängigkeit
- ✅ Schnell für kleine Änderungen
- ✅ Einfaches Rollback zu vorherigen Releases
## Deployment-Architektur
### Release Structure
```
/home/deploy/michaelschiemer/
├── releases/
│ ├── 1761499893/ # Timestamp-based releases
│ ├── v1.0.0/ # Git tag-based releases
│ └── v1.2.3/
├── shared/ # Shared zwischen Releases
│ ├── storage/
│ │ └── sessions/
│ ├── public/
│ │ └── uploads/
│ └── .env.production # Shared config
├── current -> releases/v1.2.3 # Symlink auf aktives Release
└── deploy.log # Deployment history
```
### Zero-Downtime Process
```
1. Build Assets (local)
2. Rsync to new release directory
3. Create symlinks zu shared directories
4. Start Docker containers
5. Health Check (3 retries)
├─ Success → Switch 'current' symlink (atomic)
└─ Failure → Rollback zu previous release
6. Cleanup old releases (keep last 5)
```
## Voraussetzungen
### 1. SSH Key Setup
SSH Keys für Production Server müssen konfiguriert sein:
```bash
# SSH config in ~/.ssh/config
Host michaelschiemer-prod
HostName 94.16.110.151
User deploy
IdentityFile ~/.ssh/production
StrictHostKeyChecking no
```
### 2. Production Server Requirements
- **User**: `deploy` user mit sudo Rechten
- **Docker**: Docker und Docker Compose installiert
- **Directory**: `/home/deploy/michaelschiemer` mit korrekten Permissions
### 3. Local Development Setup
- **Composer**: Für `composer install`
- **NPM**: Für `npm run build`
- **Git**: Für Tag-basiertes Release Management (optional)
- **Ansible**: Ansible ≥2.13 installiert
## Deployment-Workflows
### Standard Deployment (Timestamp-based)
Deployiert aktuellen Stand ohne Git Tag:
```bash
cd deployment/infrastructure
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml
```
**Release Name**: Unix Timestamp (z.B. `1761499893`)
### Tagged Release Deployment (Recommended)
Deployiert spezifischen Git Tag:
```bash
# Option 1: Tag explizit angeben
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml \
--extra-vars "release_tag=v1.2.3"
# Option 2: Aktuellen Git Tag verwenden (auto-detected)
git tag v1.2.3
git push origin v1.2.3
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml
```
**Release Name**: Git Tag (z.B. `v1.2.3`)
### Force Deployment (Override Lock)
Wenn ein Deployment Lock existiert:
```bash
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml \
--extra-vars "force_deploy=true"
```
## Release Management
### Git Tag Workflow
**Semantic Versioning** wird empfohlen:
```bash
# 1. Create Git tag
git tag -a v1.2.3 -m "Release v1.2.3: Feature XYZ"
git push origin v1.2.3
# 2. Deploy tagged release
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml \
--extra-vars "release_tag=v1.2.3"
# 3. Verify deployment
ssh deploy@94.16.110.151 'ls -la /home/deploy/michaelschiemer/releases/'
```
### Auto-Detection von Git Tags
Wenn `release_tag` nicht angegeben wird, versucht das Playbook automatisch den aktuellen Git Tag zu verwenden:
```bash
# Auf einem getaggten Commit
git describe --tags --exact-match # Zeigt: v1.2.3
# Deployment verwendet automatisch v1.2.3 als Release Name
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml
```
**Fallback**: Wenn kein Git Tag vorhanden → Timestamp als Release Name
### Release List anzeigen
```bash
ansible -i inventories/production/hosts.yml web_servers -b -a \
"ls -lt /home/deploy/michaelschiemer/releases | head -10"
```
**Output**:
```
total 20
drwxr-xr-x 10 deploy deploy 4096 Oct 26 18:50 v1.2.3
drwxr-xr-x 10 deploy deploy 4096 Oct 25 14:32 v1.2.2
drwxr-xr-x 10 deploy deploy 4096 Oct 24 10:15 1761499893
drwxr-xr-x 10 deploy deploy 4096 Oct 23 09:00 v1.2.1
lrwxrwxrwx 1 deploy deploy 56 Oct 26 18:50 current -> /home/deploy/michaelschiemer/releases/v1.2.3
```
## Rollback Mechanisms
### Automatic Rollback
Bei Health Check Failure rollback das Playbook automatisch:
1. **Stop failed release containers**
2. **Switch `current` symlink** zurück zu `previous_release`
3. **Start previous release containers**
4. **Remove failed release directory**
5. **Log rollback event**
**Trigger**: Health Check Status ≠ 200 (nach 3 Retries mit 5s delay)
### Manual Rollback
Manueller Rollback zu vorherigem Release:
```bash
# 1. List available releases
ansible -i inventories/production/hosts.yml web_servers -b -a \
"ls -lt /home/deploy/michaelschiemer/releases"
# 2. Identify target release (z.B. v1.2.2)
TARGET_RELEASE="v1.2.2"
# 3. Manual rollback via Ansible
ansible -i inventories/production/hosts.yml web_servers -b -m shell -a "
cd /home/deploy/michaelschiemer && \
docker compose -f current/docker-compose.yml -f current/docker-compose.production.yml down && \
ln -sfn releases/${TARGET_RELEASE} current && \
docker compose -f current/docker-compose.yml -f current/docker-compose.production.yml up -d
"
# 4. Verify rollback
curl -k https://94.16.110.151/health/summary
```
**Oder**: Erstelle Rollback Playbook:
```yaml
# playbooks/rollback-rsync.yml
- name: Manual Rollback to Previous Release
hosts: web_servers
become: true
vars:
app_name: michaelschiemer
app_user: deploy
app_base_path: "/home/{{ app_user }}/{{ app_name }}"
target_release: "{{ rollback_target }}" # --extra-vars "rollback_target=v1.2.2"
tasks:
- name: Stop current release
command: docker compose down
args:
chdir: "{{ app_base_path }}/current"
become_user: "{{ app_user }}"
- name: Switch to target release
file:
src: "{{ app_base_path }}/releases/{{ target_release }}"
dest: "{{ app_base_path }}/current"
state: link
force: yes
- name: Start target release
command: docker compose -f docker-compose.yml -f docker-compose.production.yml up -d
args:
chdir: "{{ app_base_path }}/current"
become_user: "{{ app_user }}"
- name: Health check
uri:
url: "https://{{ ansible_host }}/health/summary"
method: GET
status_code: 200
validate_certs: no
retries: 3
delay: 5
# Usage:
# ansible-playbook -i inventories/production/hosts.yml playbooks/rollback-rsync.yml --extra-vars "rollback_target=v1.2.2"
```
## Health Checks
### Configured Health Endpoints
**Primary Health Check**: `https://{{ ansible_host }}/health/summary`
**Retry Strategy**:
- Retries: 3
- Delay: 5 seconds
- Success: HTTP 200 status code
### Health Check Flow
```yaml
- name: Health check - Summary endpoint (HTTPS)
uri:
url: "https://{{ ansible_host }}/health/summary"
method: GET
return_content: yes
status_code: 200
validate_certs: no
follow_redirects: none
register: health_check
retries: 3
delay: 5
until: health_check.status == 200
ignore_errors: yes
- name: Rollback on health check failure
block:
- name: Stop failed release containers
- name: Switch symlink back to previous release
- name: Start previous release containers
- name: Remove failed release
- name: Log rollback
- name: Fail deployment
when: health_check.status != 200
```
### Custom Health Endpoints
Füge weitere Health Checks hinzu:
```yaml
# Nach der Primary Health Check in deploy-rsync-based.yml
- name: Health check - Database connectivity
uri:
url: "https://{{ ansible_host }}/health/database"
method: GET
status_code: 200
validate_certs: no
retries: 2
delay: 3
ignore_errors: yes
register: db_health_check
- name: Health check - Cache service
uri:
url: "https://{{ ansible_host }}/health/cache"
method: GET
status_code: 200
validate_certs: no
retries: 2
delay: 3
ignore_errors: yes
register: cache_health_check
- name: Aggregate health check results
set_fact:
overall_health: "{{ health_check.status == 200 and db_health_check.status == 200 and cache_health_check.status == 200 }}"
- name: Rollback on any health check failure
block:
# ... rollback steps ...
when: not overall_health
```
## Monitoring & Logging
### Deployment Log
Alle Deployments werden geloggt:
```bash
# Deployment log anzeigen
ssh deploy@94.16.110.151 'tail -50 /home/deploy/michaelschiemer/deploy.log'
```
**Log Format**:
```
[2024-10-26T18:50:30Z] Deployment started - Release: v1.2.3 - User: michael
[2024-10-26T18:50:35Z] Release: v1.2.3 | Git Hash: a1b2c3d | Commit: a1b2c3d4e5f6g7h8i9j0
[2024-10-26T18:50:50Z] Symlink switched: /home/deploy/michaelschiemer/current -> releases/v1.2.3
[2024-10-26T18:50:55Z] Health check: 200
[2024-10-26T18:50:56Z] Cleanup: Kept 5 releases, removed 1
[2024-10-26T18:50:57Z] Deployment completed successfully - Release: v1.2.3
```
### Docker Logs
```bash
# Application logs
ssh deploy@94.16.110.151 'cd /home/deploy/michaelschiemer/current && docker compose logs -f'
# Specific service
ssh deploy@94.16.110.151 'cd /home/deploy/michaelschiemer/current && docker compose logs -f php'
```
### System Monitoring
```bash
# Disk usage
ansible -i inventories/production/hosts.yml web_servers -b -a "df -h /home/deploy/michaelschiemer"
# Release directory sizes
ansible -i inventories/production/hosts.yml web_servers -b -a "du -sh /home/deploy/michaelschiemer/releases/*"
# Container status
ansible -i inventories/production/hosts.yml web_servers -b -a "docker ps"
```
## Configuration
### Shared Directories
Konfiguriert in `deploy-rsync-based.yml`:
```yaml
shared_dirs:
- storage/sessions
- public/uploads
shared_files:
- .env.production
```
**Hinweis**: `storage/logs`, `storage/cache`, `storage/uploads` werden via Docker Volumes verwaltet.
### Rsync Exclusions
Files/Directories die NICHT deployiert werden:
```yaml
rsync_excludes:
- .git/
- .github/
- node_modules/
- .env
- .env.local
- .env.development
- storage/
- public/uploads/
- tests/
- .idea/
- .vscode/
- "*.log"
- .DS_Store
- deployment/
- database.sqlite
- "*.cache"
- .php-cs-fixer.cache
- var/cache/
- var/logs/
```
### Keep Releases
Anzahl der beibehaltenen Releases:
```yaml
keep_releases: 5 # Standard: 5 Releases
```
Ändere nach Bedarf:
```bash
ansible-playbook ... --extra-vars "keep_releases=10"
```
## Troubleshooting
### Problem: Deployment Lock existiert
**Error**:
```
FAILED! => msg: Deployment already in progress. Lock file exists: /home/deploy/michaelschiemer/.deploy.lock
```
**Ursache**: Vorheriges Deployment wurde unterbrochen
**Lösung**:
```bash
# Option 1: Force deployment
ansible-playbook ... --extra-vars "force_deploy=true"
# Option 2: Lock manuell entfernen
ansible -i inventories/production/hosts.yml web_servers -b -m file \
-a "path=/home/deploy/michaelschiemer/.deploy.lock state=absent"
```
### Problem: Health Check schlägt fehl
**Error**:
```
FAILED! => Deployment failed - health check returned 503. Rolled back to previous release.
```
**Diagnose**:
```bash
# 1. Check application logs
ssh deploy@94.16.110.151 'cd /home/deploy/michaelschiemer/current && docker compose logs --tail=100'
# 2. Check container status
ssh deploy@94.16.110.151 'docker ps -a'
# 3. Manual health check
curl -k -v https://94.16.110.151/health/summary
# 4. Check deployment log
ssh deploy@94.16.110.151 'tail -100 /home/deploy/michaelschiemer/deploy.log'
```
**Häufige Ursachen**:
- .env.production fehlt oder fehlerhaft
- Database migration fehlgeschlagen
- Docker container starten nicht
- SSL Zertifikat Probleme
### Problem: Rsync zu langsam
**Symptom**: Deployment dauert mehrere Minuten
**Optimierung**:
```yaml
# In deploy-rsync-based.yml - rsync command erweitern
--compress # Kompression aktiviert
--delete-after # Löschen nach Transfer
--delay-updates # Atomic updates
```
**Alternative**: Rsync via lokales Netzwerk statt Internet:
```yaml
# Wenn Production Server im gleichen Netzwerk
ansible_host: 192.168.1.100 # Lokale IP statt öffentliche
```
### Problem: Git Tag nicht erkannt
**Symptom**: Deployment verwendet Timestamp statt Git Tag
**Diagnose**:
```bash
# Check ob auf getaggtem Commit
git describe --tags --exact-match
# Sollte: v1.2.3 (ohne Fehler)
# Check ob Tag existiert
git tag -l
```
**Lösung**:
```bash
# 1. Tag erstellen falls fehlend
git tag v1.2.3
git push origin v1.2.3
# 2. Oder Tag explizit angeben
ansible-playbook ... --extra-vars "release_tag=v1.2.3"
```
## Best Practices
### 1. Always Tag Releases
```bash
# Vor Production Deployment immer Git Tag erstellen
git tag -a v1.2.3 -m "Release v1.2.3: Feature description"
git push origin v1.2.3
```
**Vorteile**:
- Klare Release-Historie
- Einfaches Rollback zu spezifischen Versionen
- Semantic Versioning tracking
### 2. Test Deployment in Staging First
```bash
# Staging deployment (separate inventory)
ansible-playbook -i inventories/staging/hosts.yml playbooks/deploy-rsync-based.yml \
--extra-vars "release_tag=v1.2.3"
# Nach erfolgreichen Tests → Production
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-rsync-based.yml \
--extra-vars "release_tag=v1.2.3"
```
### 3. Monitor Deployment Log
```bash
# Real-time deployment monitoring
ssh deploy@94.16.110.151 'tail -f /home/deploy/michaelschiemer/deploy.log'
```
### 4. Backup vor Major Releases
```bash
# Database backup vor Major Release
ssh deploy@94.16.110.151 'cd /home/deploy/michaelschiemer/current && \
docker compose exec php php console.php db:backup'
```
### 5. Verify Health Before Release Tag
```bash
# Health check auf Staging
curl -k https://staging.michaelschiemer.de/health/summary
# Bei Erfolg → Production Tag
git tag v1.2.3
git push origin v1.2.3
```
## Comparison: Rsync vs Git-based
### Rsync-based (Current)
**Vorteile**:
- ✅ Keine Git-Server Abhängigkeit
- ✅ Funktioniert mit lokalem Development
- ✅ Schnell für kleine Änderungen
- ✅ Einfaches Setup
- ✅ Git Tag Support ohne External Server
**Nachteile**:
- ❌ Keine Git-Historie auf Production Server
- ❌ Erfordert lokale Build-Steps (Composer, NPM)
- ❌ Rsync über Internet kann langsam sein
### Git-based
**Vorteile**:
- ✅ Git-Historie auf Production Server
- ✅ Atomare Releases mit Git Commits
- ✅ Build direkt auf Production Server
- ✅ Kein lokales Build erforderlich
**Nachteile**:
- ❌ Gitea Server muss öffentlich erreichbar sein
- ❌ Zusätzliche Infrastruktur (Gitea)
- ❌ SSH Key Management komplexer
## Performance Optimizations
### 1. Pre-built Assets
Assets werden lokal gebaut → schnelleres Deployment:
```yaml
pre_tasks:
- name: Install Composer dependencies locally
- name: Build NPM assets locally
```
### 2. Docker Layer Caching
Docker Images werden auf Production Server gecached → schnellerer Start.
### 3. Shared Directories
Shared directories vermeiden unnötiges Kopieren:
- `storage/sessions`
- `public/uploads`
- `.env.production`
### 4. Cleanup Old Releases
Nur 5 Releases behalten → spart Disk Space:
```yaml
keep_releases: 5
```
## Related Files
- `deploy-rsync-based.yml` - Rsync-based Deployment Playbook
- `deploy-git-based.yml` - Git-based Deployment Playbook (Alternative)
- `rollback-git-based.yml` - Git-based Rollback Playbook
- `inventories/production/hosts.yml` - Production Server Configuration
## Zusammenfassung
Das rsync-based Deployment bietet:
-**Production-Ready** Zero-Downtime Deployment
-**Git Tag Support** für klare Release-Historie
-**Automatischer Rollback** bei Failures
-**Einfaches Setup** ohne externe Dependencies
-**Schnell und Zuverlässig** für Development und Production
**Empfehlung**: Ideal für lokale Development → Production Workflows ohne zusätzliche Git-Server-Infrastruktur.

View File

@@ -1,6 +1,11 @@
---
# Git-based Deployment Playbook with Releases/Symlink Pattern
# Git-based Deployment Playbook with Releases/Symlink Pattern (Gitea)
# Implements production-ready deployment with zero-downtime and rollback support
# Uses Gitea as Git repository server with SSH-based authentication
#
# Prerequisites:
# - SSH deploy key must be placed in deployment/infrastructure/secrets/gitea_deploy_key
# - Deploy key must be added to Gitea repository or user account
#
# Usage:
# ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-git-based.yml
@@ -23,9 +28,11 @@
shared_path: "{{ app_base_path }}/shared"
current_path: "{{ app_base_path }}/current"
# Git configuration
git_repo: "https://github.com/michaelschiemer/michaelschiemer.git"
# Git configuration (Gitea)
# Use localhost for local testing, git.michaelschiemer.de for production
git_repo: "git@localhost:michael/michaelschiemer.git"
git_branch: "{{ release_tag | default('main') }}"
git_ssh_key: "/home/{{ app_user }}/.ssh/gitea_deploy_key"
# Release configuration
release_timestamp: "{{ ansible_date_time.epoch }}"
@@ -47,7 +54,72 @@
shared_files:
- .env.production
pre_tasks:
tasks:
# ==========================================
# 1. SSH Key Setup for Gitea Access
# ==========================================
- name: Create .ssh directory for deploy user
file:
path: "/home/{{ app_user }}/.ssh"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0700'
- name: Copy Gitea deploy SSH private key
copy:
src: "{{ playbook_dir }}/../secrets/gitea_deploy_key"
dest: "{{ git_ssh_key }}"
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0600'
- name: Copy Gitea deploy SSH public key
copy:
src: "{{ playbook_dir }}/../secrets/gitea_deploy_key.pub"
dest: "{{ git_ssh_key }}.pub"
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0644'
- name: Configure SSH for Gitea (disable StrictHostKeyChecking)
blockinfile:
path: "/home/{{ app_user }}/.ssh/config"
create: yes
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0600'
marker: "# {mark} ANSIBLE MANAGED BLOCK - Gitea SSH Config"
block: |
Host localhost
HostName localhost
Port 2222
User git
IdentityFile {{ git_ssh_key }}
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
Host git.michaelschiemer.de
HostName git.michaelschiemer.de
Port 2222
User git
IdentityFile {{ git_ssh_key }}
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
# ==========================================
# 2. Directory Structure Setup
# ==========================================
- name: Create base application directory
file:
path: "{{ app_base_path }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
- name: Check if deployment lock exists
stat:
path: "{{ app_base_path }}/.deploy.lock"
@@ -74,19 +146,6 @@
owner: "{{ app_user }}"
group: "{{ app_group }}"
tasks:
# ==========================================
# 1. Directory Structure Setup
# ==========================================
- name: Create base application directory
file:
path: "{{ app_base_path }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
- name: Create releases directory
file:
path: "{{ releases_path }}"

View File

@@ -28,7 +28,8 @@
# Release configuration
release_timestamp: "{{ ansible_date_time.epoch }}"
release_name: "{{ release_tag | default(release_timestamp) }}"
# Note: effective_release_tag is set in pre_tasks based on Git tags
release_name: "{{ effective_release_tag | default(release_tag | default(release_timestamp)) }}"
release_path: "{{ releases_path }}/{{ release_name }}"
# Deployment settings
@@ -66,8 +67,46 @@
- .php-cs-fixer.cache
- var/cache/
- var/logs/
- "*.php85/"
- src/**/*.php85/
pre_tasks:
# Git Tag Detection and Validation
- name: Get current Git tag (if release_tag not specified)
local_action:
module: command
cmd: git describe --tags --exact-match
chdir: "{{ local_project_path }}"
register: git_current_tag
become: false
ignore_errors: yes
when: release_tag is not defined
- name: Get current Git commit hash
local_action:
module: command
cmd: git rev-parse --short HEAD
chdir: "{{ local_project_path }}"
register: git_commit_hash
become: false
- name: Set release_name from Git tag or timestamp
set_fact:
effective_release_tag: "{{ release_tag | default(git_current_tag.stdout if (git_current_tag is defined and git_current_tag.rc == 0) else release_timestamp) }}"
git_hash: "{{ git_commit_hash.stdout }}"
- name: Display deployment information
debug:
msg:
- "=========================================="
- "Deployment Information"
- "=========================================="
- "Release: {{ effective_release_tag }}"
- "Git Hash: {{ git_hash }}"
- "Source: {{ local_project_path }}"
- "Target: {{ ansible_host }}"
- "=========================================="
- name: Install Composer dependencies locally before deployment
local_action:
module: command
@@ -155,6 +194,11 @@
# 2. Rsync Application Code to New Release
# ==========================================
- name: Remove old release directory if exists (prevent permission issues)
file:
path: "{{ release_path }}"
state: absent
- name: Create new release directory
file:
path: "{{ release_path }}"
@@ -163,16 +207,25 @@
group: "{{ app_group }}"
mode: '0755'
- name: Sync application code to new release via rsync
synchronize:
src: "{{ local_project_path }}/"
dest: "{{ release_path }}/"
delete: yes
recursive: yes
rsync_opts: "{{ rsync_excludes | map('regex_replace', '^(.*)$', '--exclude=\\1') | list }}"
private_key: "{{ ansible_ssh_private_key_file }}"
- name: Temporarily rename .dockerignore to prevent rsync -F from reading it
command: mv {{ local_project_path }}/.dockerignore {{ local_project_path }}/.dockerignore.bak
delegate_to: localhost
become: false
ignore_errors: yes
- name: Sync application code to new release via rsync (raw command to avoid -F flag)
command: >
rsync --delay-updates --compress --delete-after --archive --rsh='ssh -i {{ ansible_ssh_private_key_file }} -o StrictHostKeyChecking=no' --no-g --no-o
{% for exclude in rsync_excludes %}--exclude='{{ exclude }}' {% endfor %}
{{ local_project_path }}/ {{ app_user }}@{{ ansible_host }}:{{ release_path }}/
delegate_to: localhost
become: false
- name: Restore .dockerignore after rsync
command: mv {{ local_project_path }}/.dockerignore.bak {{ local_project_path }}/.dockerignore
delegate_to: localhost
become: false
ignore_errors: yes
- name: Set correct ownership for release
file:
@@ -191,10 +244,10 @@
changed_when: false
failed_when: false
- name: Log commit hash
- name: Log release and commit information
lineinfile:
path: "{{ app_base_path }}/deploy.log"
line: "[{{ ansible_date_time.iso8601 }}] Commit: {{ commit_hash.stdout | default('N/A - not a git repository') }}"
line: "[{{ ansible_date_time.iso8601 }}] Release: {{ effective_release_tag }} | Git Hash: {{ git_hash | default('N/A') }} | Commit: {{ commit_hash.stdout | default('N/A') }}"
when: commit_hash.rc == 0
# ==========================================
@@ -325,6 +378,29 @@
path: "{{ app_base_path }}/deploy.log"
line: "[{{ ansible_date_time.iso8601 }}] Symlink switched: {{ current_path }} -> {{ release_path }}"
# ==========================================
# 8.5. SSL Certificate Setup
# ==========================================
- name: Create SSL directory in release
file:
path: "{{ release_path }}/ssl"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
- name: Copy SSL certificates from certbot to release (if they exist)
shell: |
if docker ps | grep -q certbot; then
docker cp certbot:/etc/letsencrypt/archive/michaelschiemer.de/fullchain1.pem {{ release_path }}/ssl/fullchain.pem 2>/dev/null || true
docker cp certbot:/etc/letsencrypt/archive/michaelschiemer.de/privkey1.pem {{ release_path }}/ssl/privkey.pem 2>/dev/null || true
chown {{ app_user }}:{{ app_group }} {{ release_path }}/ssl/*.pem 2>/dev/null || true
fi
args:
chdir: "{{ current_path }}"
ignore_errors: yes
# ==========================================
# 9. Start Docker Containers
# ==========================================
@@ -344,16 +420,17 @@
# ==========================================
- name: Wait for application to be ready
wait_for:
timeout: 10
delegate_to: localhost
pause:
seconds: 10
- name: Health check - Summary endpoint
- name: Health check - Summary endpoint (HTTPS)
uri:
url: "http://{{ ansible_host }}/health/summary"
url: "https://{{ ansible_host }}/health/summary"
method: GET
return_content: yes
status_code: 200
validate_certs: no
follow_redirects: none
register: health_check
retries: 3
delay: 5

View File

@@ -0,0 +1,3 @@
# SECURITY: Never commit SSH keys or secrets to version control!
*
!.gitignore