Erscheinungsbild
Deployment
Zwei Zielsysteme, entschieden in deployment.target:
| Ziel | Wofür |
|---|---|
mittwald-static | Der Regelfall. Apache, .htaccess, PHP-Formulare möglich. |
github-pages | Projekte ohne PHP-Formulare — und mit eigener Domain. |
Die Wahl entscheidet mehr als das Ziel der Übertragung: Bei mittwald-static entsteht beim Bauen eine .htaccess mit Weiterleitungen, Cache- und Sicherheits-Headern. GitHub Pages liest keine — dort gibt es diese Header nicht (ADR 0012).
Was übertragen wird
Bei provider: 'standalone-php' sind es zwei Dinge: der Inhalt von dist/ und das Verzeichnis des Formular-Handlers. Der Handler braucht dabei drei Handgriffe, die eine reine Dateikopie nicht leistet — composer install --no-dev, eine wirksame .htaccess und ein beschreibbares Ablageverzeichnis. Die Einzelheiten stehen bei ihm: Standalone-PHP → Ausliefern.
Zwei Dinge liegen auf dem Server und werden nie überschrieben:
| Was | Warum |
|---|---|
config/config.local.php | Enthält das SMTP-Passwort |
var/ | Rate-Limit-Zähler, Kennungen, Log |
Der Workflow nimmt beide vom rsync aus. Wer von Hand kopiert, muss daran denken.
Der Deployment-Plan
Was übertragen wird, entscheidet nicht der Workflow, sondern der Build. Er legt neben die Ausgabe eine Datei .deploy-plan.json:
json
{
"target": "mittwald-static",
"environment": "production",
"site": "https://www.kunde.de",
"host": "www.kunde.de",
"formsProvider": "standalone-php",
"shipHandler": true,
"handlerPath": "/api/forms",
"assetsDir": "_astro"
}Der Workflow liest sie und bricht ab, wenn das Zielsystem nicht zu ihm passt. Damit gibt es keine zweite Stelle, an der steht, ob ein Projekt ein PHP-Backend hat — die gefährliche Abweichung wäre ein hochgeladener, unkonfigurierter Formular-Endpunkt auf einem Server, der keinen haben soll (ADR 0014).
Die Datei ist eine Bauzeit-Information und gehört nicht auf den Server. Sie beginnt mit einem Punkt: Damit überschreiben sich die Fixture-Builds nicht gegenseitig, der rsync-Aufruf nimmt sie aus, und die erzeugte .htaccess sperrt Dateien mit führendem Punkt zusätzlich gegen HTTP-Zugriff.
Umgebungen
| Environment | PUBLIC_ENVIRONMENT | Indexierbar |
|---|---|---|
production | production | ja |
staging | preview | nein |
Die Zuordnung steht im Workflow und nicht in einer Variable. Indexierbar ist ausschließlich production (ADR 0006); wäre der Wert konfigurierbar, wäre eine Baustelle in den Suchergebnissen ein Tippfehler weit weg. Der Workflow prüft zusätzlich, dass der gebaute Stand und das Ziel übereinstimmen.
Auslöser
sh
# Regelfall: Der Tag löst das Deployment aus.
git tag v1.2.0 && git push origin v1.2.0
# Staging
gh workflow run deploy.yml --ref main -f environment=staging
# Rollback — dasselbe Verfahren mit einem früheren Stand
gh workflow run deploy.yml --ref v1.1.0
# Probelauf: zeigt, was übertragen würde, und ändert nichts
gh workflow run deploy.yml --ref v1.2.0 -f dryRun=trueEin Tag aus einem Workflow löst nichts aus
Der Tag muss von einem Menschen kommen — oder von einem Personal Access Token beziehungsweise einer GitHub App. Legt ein Workflow ihn mit dem voreingestellten GITHUB_TOKEN an, entsteht kein neuer Lauf: GitHub unterbindet das, um endlose Ketten zu verhindern (Dokumentation).
Das trifft, wer in einem Kundenprojekt Release Please oder ein ähnliches Werkzeug einsetzt: Der Tag entsteht, der Release erscheint, das Deployment läuft nie — und nichts davon erzeugt eine Fehlermeldung. Wer diesen Weg will, gibt dem Werkzeug ein eigenes Token:
yaml
- uses: googleapis/release-please-action@v5
with:
token: ${{ secrets.RELEASE_TOKEN }}Die Alternative ist, das Deployment nach dem Release von Hand über workflow_dispatch auszulösen.
Im Starter-Repository selbst fällt das nicht auf, weil dort beide Deployment-Workflows ohnehin abgeschaltet sind.
Rollback ist bewusst kein eigener Mechanismus. Ein releases/-Verzeichnis mit Symlink-Tausch wäre schneller, setzt aber eine Serverstruktur voraus, die von Hand eingerichtet werden muss — und deren Fehlerfall bösartig ist: Das Deployment meldet Erfolg, und die Website zeigt weiter den alten Stand. Die Begründung steht in ADR 0014.
Quality Gate
Vor jeder Übertragung ist pnpm verify für den Commit bestanden — derselbe Befehl wie lokal und in der CI. Das Deployment fährt ihn aber nicht selbst, wenn es nicht muss: Es fragt die CI nach einem erfolgreichen Lauf auf push für genau diesen Commit. Gibt es ihn, entfallen Browser-Installation und Kette, und die Zusammenfassung des Laufs nennt den CI-Lauf. Gibt es ihn nicht — Tag auf einem beliebigen Commit, abgebrochener Lauf, Rollback auf einen alten Stand —, läuft die Kette im Deployment (ADR 0019).
Steht in der Zusammenfassung „hier gefahren", obwohl der Commit auf main liegt, ist der CI-Lauf dort abgebrochen oder rot. Das ist zu klären, nicht zu wiederholen.
Die Auslieferung entsteht in jedem Fall als eigener Build, mit dem PUBLIC_ENVIRONMENT dieses Deployments: Die Kette baut dist/ als Entwicklungsstand, und davon hängen Indexierbarkeit und Tracking ab.
Zugangsdaten
Im Repository liegen keine. Der SSH-Schlüssel ist ein Secret, alles andere sind Variablen der GitHub-Umgebung — die vollständige Liste steht bei Mittwald.
Weiter
- Mittwald — Einrichten, Workflow, Rollback, Fehlerbilder
- GitHub Pages — die Alternative und ihre Grenzen
- Apache und
.htaccess— was erzeugt wird und wie man es beeinflusst - Content Security Policy — der Weg von Beobachten zu Erzwingen