Erscheinungsbild
Deployment nach Mittwald
Der Workflow .github/workflows/deploy.yml baut, prüft und überträgt per rsync über SSH. Die Begründung des Verfahrens steht in ADR 0014.
Einrichten
1. Zielverzeichnis
Bei Mittwald liegt der Dokumentenstamm unterhalb des Projektverzeichnisses, der SSH-Benutzer ist die Projektkennung:
text
/home/www/p123456/html/kunde.de/
├── index.html … aus dist/
└── api/forms/ nur bei provider: 'standalone-php'Welcher Pfad der Domain zugeordnet ist, steht in mStudio unter der Domain-Konfiguration. Der Wert gehört unverändert in DEPLOY_PATH.
2. SSH-Schlüssel
Ein eigenes Schlüsselpaar für das Deployment — nicht der persönliche Schlüssel:
sh
ssh-keygen -t ed25519 -C "deploy kunde.de" -f ./deploy_key -N ""Den öffentlichen Teil (deploy_key.pub) in mStudio beim SSH-Benutzer hinterlegen, den privaten (deploy_key) als Secret DEPLOY_SSH_KEY in die GitHub-Umgebung. Danach die lokale Kopie löschen.
3. Hostschlüssel
sh
ssh-keyscan -t rsa,ecdsa,ed25519 ssh.kunde.mittwald.deDie Ausgabe gehört vollständig in die Variable DEPLOY_HOST_KEY. Fehlt sie, bricht der Workflow ab — beabsichtigt.
Nicht abkürzen
StrictHostKeyChecking=no steht in jeder Anleitung und macht die Verschlüsselung der Verbindung wertlos: Sie schützt dann gegen niemanden, der sich dazwischenstellen kann. Den Fingerprint einmal gegen die Angabe des Hosters prüfen kostet zwei Minuten und gilt für die Lebensdauer des Servers.
4. GitHub-Umgebung
Unter Settings → Environments je Umgebung (production, bei Bedarf staging):
| Art | Name | Beispiel |
|---|---|---|
| Secret | DEPLOY_SSH_KEY | Inhalt von deploy_key |
| Variable | DEPLOY_HOST | ssh.kunde.mittwald.de |
| Variable | DEPLOY_USER | p123456 |
| Variable | DEPLOY_PATH | /home/www/p123456/html/kunde.de |
| Variable | DEPLOY_HOST_KEY | Ausgabe von ssh-keyscan |
| Variable | DEPLOY_URL | https://www.kunde.de |
| Variable | DEPLOY_PORT | nur bei abweichendem Port |
| Variable | PUBLIC_TURNSTILE_SITE_KEY | nur bei captcha: 'turnstile' |
| Secret | KT_BASIC_AUTH_USER | nur bei deployment.apache.basicAuth |
| Secret | KT_BASIC_AUTH_PASSWORD | nur bei deployment.apache.basicAuth |
DEPLOY_USER ist bei manchen Mittwald-Projekten selbst eine Zeichenkette mit @ (bei projektübergreifendem SSH-Zugriff über die neuere „Mittwald Stack"-Plattform, Format ssh-nutzer@projekt-id). Der Workflow übergibt ihn deshalb per ssh -l, nicht durch Zusammensetzen von user@host — sonst würde aus einem eingebetteten @ ein zweites, und die Verbindung schlägt fehl oder verbindet zum falschen Ziel. Der Wert steht in mStudio beim SSH-Zugang des Projekts, wortwörtlich übernehmen.
KT_BASIC_AUTH_USER/KT_BASIC_AUTH_PASSWORD sind nur nötig, wenn deployment.apache.basicAuth gesetzt ist (Apache) — ein Vorschau-Schutz vor dem Launch. Fehlen sie bei einem production-Build, bricht er ab.
DEPLOY_URL erscheint als Verweis am Deployment in GitHub. Der Turnstile Site Key ist öffentlich per Design und deshalb eine Variable; der Secret Key liegt ausschließlich beim Handler.
Für production empfiehlt sich zusätzlich ein Required reviewer: Damit ist das Deployment eine Freigabe und nicht nur ein Tag.
5. Erster Lauf als Probelauf
sh
gh workflow run deploy.yml --ref v1.0.0 -f dryRun=trueDer Lauf durchläuft alles bis zur Übertragung und zeigt dann nur, was er ändern würde. Das ist der Moment, in dem ein falscher DEPLOY_PATH auffällt — und nicht der, in dem --delete im falschen Verzeichnis arbeitet.
Was der Workflow tut
- Abhängigkeiten installieren, PHP 8.3 bereitstellen
pnpm verify— nur, wenn die CI die Kette für diesen Commit nicht nachweisen kann; dann samt Chromium (ADR 0019)pnpm run buildmit demPUBLIC_ENVIRONMENTdieser Umgebung.deploy-plan.jsonlesen und gegen die Umgebung prüfen- Bei
standalone-php:composer install --no-dev --optimize-autoloader - SSH einrichten, Hostschlüssel pinnen
dist/→ Dokumentenstamm, mit--delete, ohne das Handler-Verzeichnisserver/forms/→ Handler-Verzeichnis: nursubmit.php,src/,vendor/undconfig/— ohneconfig.local.php,var/,tests/und die Composer-Dateien
Schritt 7 und 8 sind getrennt, weil für beide Teile verschiedene Regeln gelten. Ein einzelner Aufruf mit --delete würde entweder den Handler löschen oder verwaiste Dateien einer früheren Version stehen lassen.
Nach dem ersten Deployment
Auf dem Server bleibt eine Sache von Hand zu tun: config/config.local.php anlegen. Die Vorlage config.local.php.example wird mit übertragen, die Datei selbst nie — sie enthält das SMTP-Passwort. Ohne sie antwortet der Handler mit einem Konfigurationsfehler.
Danach eine echte Übermittlung prüfen: Kommt die E-Mail an, und steht im Log ein Eintrag ohne Feldinhalte?
Rollback
sh
gh workflow run deploy.yml --ref v1.1.0Das ist derselbe Weg mit einem früheren Stand, kein zweiter Mechanismus. Es dauert so lange wie ein Deployment — weil es eines ist.
Fehlerbilder
| Symptom | Ursache |
|---|---|
| Tag gepusht, aber kein Lauf erscheint | Der Tag kam von einem Workflow mit GITHUB_TOKEN — das löst nichts aus, siehe Deployment |
Host key verification failed | DEPLOY_HOST_KEY fehlt, ist veraltet oder gehört zu einem anderen Host |
Permission denied (publickey) | Öffentlicher Schlüssel nicht beim SSH-Benutzer hinterlegt, oder DEPLOY_USER falsch |
| Deployment grün, Website unverändert | DEPLOY_PATH zeigt nicht auf den Dokumentenstamm der Domain — in mStudio gegenprüfen |
| Formular antwortet mit „Es ist ein Fehler aufgetreten" | config/config.local.php fehlt auf dem Server oder KT_FORMS_ORIGINS passt nicht zur Domain |
| Website lädt, aber ohne Gestaltung | .htaccess greift nicht — AllowOverride prüfen, siehe Apache |
deployment.target ist "github-pages" | Falscher Workflow für dieses Projekt; deploy-pages.yml ist zuständig |
| Weiterleitungsschleife | Ein zweiter HTTPS-Redirect in der Serverkonfiguration. deployment.apache.forceHttps: false setzen |
Ohne GitHub Actions
Wer von Hand überträgt, braucht dieselben Schritte:
sh
PUBLIC_ENVIRONMENT=production pnpm run build
composer install --working-dir=server/forms --no-dev --optimize-autoloader
rsync -az --delete --exclude=/.deploy-plan.json --exclude=/api/forms/ \
dist/ p123456@ssh.kunde.mittwald.de:/home/www/p123456/html/kunde.de/
rsync -az --delete --mkpath \
--exclude=config/config.local.php --exclude=var/ \
--exclude=tests/ --exclude=phpunit.xml.dist \
--exclude=composer.json --exclude=composer.lock \
server/forms/ p123456@ssh.kunde.mittwald.de:/home/www/p123456/html/kunde.de/api/forms/--mkpath bei der zweiten Übertragung: Das Handler-Verzeichnis liegt unterhalb des Dokumentenstamms und existiert beim allerersten Deployment noch nicht — ohne die Option legt rsync nur die letzte fehlende Verzeichnisebene an, nicht mehrere verschachtelte auf einmal.
Die Ausschlusslisten sind nicht optional. Ohne die erste löscht --delete das Handler-Verzeichnis, ohne die zweite das SMTP-Passwort und das Log.
Auf dem Server bleiben damit submit.php, src/, vendor/ und config/ — genau das, was der Handler zur Laufzeit braucht. Die Tests und die Composer-Dateien gehören nicht dazu: Das ist derselbe Grund, aus dem composer install mit --no-dev läuft. Die .htaccess des Verzeichnisses würde sie zwar sperren, aber auf sie allein verlässt sich diese Auslieferung an keiner Stelle.