{"id":391,"date":"2026-09-13T11:39:57","date_gmt":"2026-09-13T11:39:57","guid":{"rendered":"https:\/\/www.fabricioruch.ch\/?p=391"},"modified":"2026-09-13T11:39:57","modified_gmt":"2026-09-13T11:39:57","slug":"wordpress-themes-wie-software-behandeln-deployment-mit-github-actions","status":"publish","type":"post","link":"https:\/\/www.fabricioruch.ch\/?p=391","title":{"rendered":"WordPress-Themes wie Software behandeln: Deployment mit GitHub Actions"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Ein selbst entwickeltes WordPress-Theme ist Software.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es enth\u00e4lt PHP, JavaScript, CSS, Templates, Assets und Konfiguration. \u00c4nderungen an einer Datei k\u00f6nnen Auswirkungen auf andere Teile des Systems haben. Fehler k\u00f6nnen die komplette Website unbrauchbar machen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trotzdem werden WordPress-Themes erstaunlich oft anders behandelt als andere Software:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Theme \u00e4ndern\n    \u2193\nZIP erstellen\n    \u2193\nWordPress \u00f6ffnen\n    \u2193\nTheme hochladen\n    \u2193\nhoffen<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Oder noch direkter:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FTP\n \u2193\nDatei \u00fcberschreiben\n \u2193\nfertig<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr kleine \u00c4nderungen funktioniert das erstaunlich lange.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bis es nicht mehr funktioniert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dann entstehen unangenehme Fragen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Welcher Stand l\u00e4uft gerade auf dem Server?<\/li>\n\n\n\n<li>Entspricht dieser Stand \u00fcberhaupt noch dem lokalen Projekt?<\/li>\n\n\n\n<li>Welche Dateien wurden beim letzten Update ver\u00e4ndert?<\/li>\n\n\n\n<li>Liegen alte Dateien auf dem Server, die lokal l\u00e4ngst gel\u00f6scht wurden?<\/li>\n\n\n\n<li>Welcher Commit entspricht der Produktion?<\/li>\n\n\n\n<li>Kann der vorherige Zustand wiederhergestellt werden?<\/li>\n\n\n\n<li>Wurde das Theme vor dem Deployment \u00fcberhaupt gepr\u00fcft?<\/li>\n\n\n\n<li>Funktioniert die Website nach dem Deployment tats\u00e4chlich?<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Die L\u00f6sung ist nicht ein besserer FTP-Client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die L\u00f6sung besteht darin, das Theme wie das zu behandeln, was es ist:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>ein deploybares Software-Artefakt.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Das eigentliche Problem: Wo liegt die Wahrheit?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bei einem manuell gepflegten WordPress-Theme entstehen schnell mehrere Zust\u00e4nde:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Lokaler Rechner\n      \u2502\n      \u251c\u2500\u2500 Version A\n      \u2502\nGitHub\n      \u251c\u2500\u2500 Version B\n      \u2502\nWebserver\n      \u2514\u2500\u2500 Version C<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Solange alle drei ungef\u00e4hr gleich sind, f\u00e4llt das kaum auf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nach einigen manuellen \u00c4nderungen sieht die Realit\u00e4t m\u00f6glicherweise so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Git Repository\n\u251c\u2500\u2500 functions.php\n\u251c\u2500\u2500 style.css\n\u251c\u2500\u2500 assets\/\n\u2502   \u251c\u2500\u2500 site.css\n\u2502   \u2514\u2500\u2500 browser.js\n\u2514\u2500\u2500 templates\/\n\n\nProduktionsserver\n\u251c\u2500\u2500 functions.php\n\u251c\u2500\u2500 style.css\n\u251c\u2500\u2500 assets\/\n\u2502   \u251c\u2500\u2500 site.css\n\u2502   \u251c\u2500\u2500 browser.js\n\u2502   \u251c\u2500\u2500 category-sort.js\n\u2502   \u2514\u2500\u2500 old-navigation.js\n\u2514\u2500\u2500 templates\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die letzten beiden JavaScript-Dateien wurden im Repository l\u00e4ngst entfernt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auf dem Server existieren sie aber weiterhin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein klassischer Datei-Upload kennt keinen gew\u00fcnschten Gesamtzustand. Er kopiert Dateien.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Deployment sollte etwas anderes tun:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Der Zustand im Repository definiert den Zustand des deployten Themes.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Damit wird Git zum <strong>Source of Truth<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Git Repository\n      \u2502\n      \u2502 Source of Truth\n      \u25bc\nValidation\n      \u2502\n      \u25bc\nDeployment\n      \u2502\n      \u25bc\nWordPress Production<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Produktion wird aus Git hergestellt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nicht umgekehrt.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Von Datei\u00fcbertragung zu CI\/CD<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">An dieser Stelle lohnt sich die Trennung zweier Begriffe, die h\u00e4ufig zusammengeworfen werden:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Continuous Integration<\/li>\n\n\n\n<li>Continuous Delivery beziehungsweise Continuous Deployment<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr ein WordPress-Theme l\u00e4sst sich die Trennung sehr konkret zeigen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Continuous Integration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CI beantwortet die Frage:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Ist dieser Stand grunds\u00e4tzlich deploybar?<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Beispiel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Commit\n  \u2502\n  \u25bc\nPHP Syntax Check\n  \u2502\n  \u25bc\nJavaScript Syntax Check\n  \u2502\n  \u25bc\nTheme Structure Check\n  \u2502\n  \u25bc\nweitere Tests<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Erst wenn diese Pr\u00fcfungen erfolgreich sind, darf der n\u00e4chste Schritt beginnen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Continuous Deployment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CD beantwortet anschlie\u00dfend:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Wie kommt der gepr\u00fcfte Stand kontrolliert auf das Produktionssystem?<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>Validierter Commit\n       \u2502\n       \u25bc\nSSH-Verbindung\n       \u2502\n       \u25bc\nBackup\n       \u2502\n       \u25bc\nDeployment\n       \u2502\n       \u25bc\nSmoke Tests<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions kann beide Aufgaben \u00fcbernehmen.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Die einfachste m\u00f6gliche GitHub Action<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Eine sehr kleine Deployment-Pipeline k\u00f6nnte so aussehen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Deploy WordPress Theme\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v4\n\n      - name: Deploy\n        run: |\n          rsync -az --delete \\\n            .\/ \\\n            user@server:\/path\/to\/wp-content\/themes\/my-theme\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das Prinzip ist bereits sichtbar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Push auf main\n      \u2193\nGitHub Actions\n      \u2193\nRepository auschecken\n      \u2193\nrsync\n      \u2193\nProduktionsserver<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aber produktionsreif ist das noch nicht.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vor allem fehlen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Validierung<\/li>\n\n\n\n<li>sichere Credentials<\/li>\n\n\n\n<li>Schutz des Deployment-Ziels<\/li>\n\n\n\n<li>Vorschau der \u00c4nderungen<\/li>\n\n\n\n<li>Backup<\/li>\n\n\n\n<li>Verifikation nach dem Deployment<\/li>\n\n\n\n<li>brauchbares Feedback<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Diese Punkte machen aus einem automatisierten Datei-Upload eine Deployment-Pipeline.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">SSH statt Zugangsdaten im Workflow<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions muss sich am Webserver authentifizieren k\u00f6nnen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Daf\u00fcr eignet sich ein separater SSH-Key.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Ed25519-Key kann beispielsweise erzeugt werden mit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keygen \\\n  -t ed25519 \\\n  -C \"github-actions-wordpress\" \\\n  -f github-actions-wordpress<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Es entstehen zwei Dateien:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>github-actions-wordpress\ngithub-actions-wordpress.pub<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die <code>.pub<\/code>-Datei enth\u00e4lt den \u00f6ffentlichen Schl\u00fcssel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dieser wird auf dem Server autorisiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der private Schl\u00fcssel geh\u00f6rt dagegen weder in das Theme noch in das Repository.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">GitHub Secrets<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions stellt daf\u00fcr Repository Secrets bereit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr ein einfaches WordPress-Deployment k\u00f6nnen beispielsweise folgende Werte ben\u00f6tigt werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SSH_HOST\nSSH_USER\nSSH_PRIVATE_KEY\nTHEME_PATH<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Zielpfad k\u00f6nnte konzeptionell so aussehen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/home\/user\/public_html\/wp-content\/themes\/my-theme<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Workflow kann anschlie\u00dfend darauf zugreifen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>${{ secrets.SSH_HOST }}\n${{ secrets.SSH_USER }}\n${{ secrets.SSH_PRIVATE_KEY }}\n${{ secrets.THEME_PATH }}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der private Schl\u00fcssel taucht damit nicht im Repository auf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist ein grundlegendes Prinzip von Deployment-Konfiguration:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Code geh\u00f6rt ins Repository. Secrets nicht.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">SSH vorbereiten<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Der Runner von GitHub Actions existiert nur f\u00fcr die Dauer des Jobs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die SSH-Konfiguration muss deshalb w\u00e4hrend des Workflows erstellt werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Configure SSH\n  shell: bash\n  run: |\n    mkdir -p ~\/.ssh\n    chmod 700 ~\/.ssh\n\n    printf '%s\\n' \\\n      \"${{ secrets.SSH_PRIVATE_KEY }}\" \\\n      &gt; ~\/.ssh\/id_ed25519\n\n    chmod 600 ~\/.ssh\/id_ed25519\n\n    ssh-keyscan \\\n      -H \"${{ secrets.SSH_HOST }}\" \\\n      &gt;&gt; ~\/.ssh\/known_hosts<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Danach kann der Workflow den Server erreichen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein separater Verbindungstest ist sinnvoll:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Test SSH connection\n  run: |\n    ssh user@server \"\n      echo 'SSH connection established'\n      hostname\n      whoami\n    \"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das wirkt zun\u00e4chst redundant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die Fehlersuche ist die Trennung aber wertvoll.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Scheitert sp\u00e4ter <code>rsync<\/code>, ist bereits bekannt, ob grunds\u00e4tzlich ein SSH-Problem vorliegt oder tats\u00e4chlich das Deployment fehlschl\u00e4gt.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Vor dem Deployment muss das Theme gepr\u00fcft werden<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Automatisierung sollte Fehler nicht nur schneller ausliefern.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sie sollte bekannte Fehlerklassen verhindern.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bei einem klassischen WordPress-Theme k\u00f6nnen bereits sehr einfache Pr\u00fcfungen viel abfangen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">PHP-Syntax pr\u00fcfen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PHP besitzt daf\u00fcr bereits einen eingebauten Syntaxcheck:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php -l functions.php<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr alle PHP-Dateien:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>find . \\\n  -type f \\\n  -name \"*.php\" \\\n  -print0 \\\n  | xargs -0 -n1 php -l<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Syntaxfehler f\u00fchrt zum Abbruch der Pipeline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Damit kann beispielsweise ein versehentlich fehlendes Semikolon nicht mehr einfach auf Produktion landen.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">JavaScript pr\u00fcfen<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Node kann JavaScript-Dateien zumindest syntaktisch pr\u00fcfen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --check assets\/js\/browser.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Automatisiert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>for file in $(find assets -type f -name \"*.js\"); do\n    node --check \"$file\"\ndone<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das ersetzt keine Tests und keinen Linter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es verhindert aber bereits triviale Syntaxfehler.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">WordPress-Struktur pr\u00fcfen<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Auch die erwartete Theme-Struktur l\u00e4sst sich kontrollieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Beispiel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>REQUIRED_FILES=(\n    \"style.css\"\n    \"functions.php\"\n    \"index.php\"\n    \"header.php\"\n    \"footer.php\"\n    \"404.php\"\n)\n\nfor FILE in \"${REQUIRED_FILES&#91;@]}\"; do\n    if &#91; ! -f \"$FILE\" ]; then\n        echo \"Missing: $FILE\"\n        exit 1\n    fi\ndone<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit l\u00e4sst sich eine weitere Klasse unangenehmer Fehler verhindern:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Eine zentrale Datei wurde beim Refactoring versehentlich gel\u00f6scht oder nicht committed.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Man kann noch weitergehen und beispielsweise leere PHP-Templates erkennen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if &#91; ! -s \"$FILE\" ]; then\n    echo \"Template is empty: $FILE\"\n    exit 1\nfi<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist simpel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Aber genau solche simplen Guardrails sind wertvoll.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Poka-Yoke in der Deployment-Pipeline<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Das Prinzip dahinter ist dasselbe wie bei Poka-Yoke in Softwarearchitektur und Entwicklungsprozessen:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Einen vermeidbaren Fehler nicht dokumentieren, sondern technisch erschweren oder unm\u00f6glich machen.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Eine README k\u00f6nnte sagen:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Vor dem Deployment bitte kontrollieren, ob <code>index.php<\/code> vorhanden ist.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Eine Pipeline kann stattdessen sagen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>index.php fehlt\n      \u2193\nPipeline rot\n      \u2193\nkein Deployment<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die zweite Variante ist zuverl\u00e4ssiger.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Warum <code>rsync<\/code>?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr ein klassisches WordPress-Theme ist <code>rsync<\/code> ein sehr passendes Werkzeug.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es synchronisiert Verzeichnisse effizient und \u00fcbertr\u00e4gt nur notwendige \u00c4nderungen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein typischer Aufruf:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rsync -az \\\n  .\/ \\\n  user@server:\/path\/to\/theme\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Interessant wird insbesondere die Option:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>--delete<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit werden Dateien auf dem Ziel entfernt, die im Quellverzeichnis nicht mehr existieren.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Warum <code>--delete<\/code> wichtig ist<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Nehmen wir an, ein Theme enthielt fr\u00fcher:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>assets\/js\/\n\u251c\u2500\u2500 browser.js\n\u251c\u2500\u2500 category-sort.js\n\u2514\u2500\u2500 old-navigation.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nach einem Refactoring existiert im Repository nur noch:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>assets\/js\/\n\u2514\u2500\u2500 browser.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ein normaler Upload \u00fcberschreibt m\u00f6glicherweise <code>browser.js<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die anderen Dateien bleiben aber auf dem Server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rsync --delete<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">wird das Ziel tats\u00e4chlich synchronisiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Danach gilt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Repository\n    =\nProduktionsverzeichnis<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist genau das gew\u00fcnschte Verhalten.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Warum <code>--delete<\/code> gleichzeitig gef\u00e4hrlich ist<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Angenommen, der konfigurierte Zielpfad lautet versehentlich:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/wp-content\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">statt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/wp-content\/themes\/my-theme\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dann bekommt der Begriff <code>--delete<\/code> pl\u00f6tzlich eine v\u00f6llig andere Bedeutung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deshalb sollte eine produktive Pipeline das Deployment-Ziel \u00fcberpr\u00fcfen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>TARGET=\"${THEME_PATH}\"\nEXPECTED_SUFFIX=\"\/wp-content\/themes\/my-theme\"\n\nif &#91;&#91; \"$TARGET\" != *\"$EXPECTED_SUFFIX\" ]]; then\n    echo \"Unsafe deployment target\"\n    exit 1\nfi<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Erst danach darf <code>rsync --delete<\/code> ausgef\u00fchrt werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist wieder Poka-Yoke:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Konfigurationsfehler\n       \u2193\nGuard erkennt falschen Pfad\n       \u2193\nDeployment wird gestoppt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">statt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Konfigurationsfehler\n       \u2193\nrsync --delete\n       \u2193\nProblem<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Erst ansehen, dann ver\u00e4ndern: <code>rsync --dry-run<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ein weiterer sinnvoller Guardrail ist ein Dry Run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>rsync<\/code> kann berechnen, was passieren w\u00fcrde, ohne tats\u00e4chlich etwas zu ver\u00e4ndern:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rsync -azn \\\n  --delete \\\n  --itemize-changes \\\n  .\/ \\\n  user@server:\/path\/to\/theme\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das <code>n<\/code> steht f\u00fcr Dry Run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>--itemize-changes<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">wird zus\u00e4tzlich sichtbar, welche \u00c4nderungen geplant sind.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt;f.st...... assets\/css\/site.css\n&gt;f.st...... functions.php\n&gt;f+++++++++ inc\/helpers.php\n*deleting   assets\/js\/category-sort.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit besitzt die Pipeline vor dem eigentlichen Deployment bereits eine Art \u00c4nderungsplan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist insbesondere nach gr\u00f6\u00dferen Refactorings hilfreich.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Backup vor Deployment<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Validierung reduziert das Risiko.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sie beseitigt es nicht.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deshalb sollte unmittelbar vor dem Deployment der aktuelle Produktionsstand gesichert werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Backupname kann beispielsweise enthalten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>my-theme-20260913-112500-a91e42f<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Darin stecken:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Theme\nTimestamp\nCommit<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Konzeptionell:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Aktuelles Production Theme\n          \u2502\n          \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba Backup\n          \u2502\n          \u25bc\n     neues Deployment<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ein einfacher serverseitiger Backup-Schritt k\u00f6nnte so aussehen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>TIMESTAMP=$(date -u +\"%Y%m%d-%H%M%S\")\n\ncp -a \\\n  \/path\/to\/theme \\\n  \/path\/to\/backups\/theme-${TIMESTAMP}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit h\u00e4ngt die Wiederherstellung nicht davon ab, ob irgendwo noch eine passende ZIP-Datei existiert.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Backups brauchen eine Retention Policy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Automatische Backups erzeugen ein neues Problem:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sie funktionieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bei jedem Deployment entsteht ein weiteres Verzeichnis.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nach gen\u00fcgend Deployments sieht der Server so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>backups\/\n\u251c\u2500\u2500 theme-001\n\u251c\u2500\u2500 theme-002\n\u251c\u2500\u2500 theme-003\n\u251c\u2500\u2500 ...\n\u251c\u2500\u2500 theme-184\n\u2514\u2500\u2500 theme-185<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Deshalb geh\u00f6rt zur Backup-Strategie auch eine Retention Policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Die letzten zehn Deployments werden behalten.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c4ltere Backups werden automatisch entfernt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das Prinzip ist allgemeiner als WordPress:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Ein Backup-Konzept definiert nicht nur, wie Sicherungen entstehen, sondern auch, wie lange sie existieren.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Das eigentliche Deployment<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Nach Validierung, Zielpr\u00fcfung, Dry Run und Backup kann synchronisiert werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rsync -az \\\n  --delete \\\n  --itemize-changes \\\n  --stats \\\n  --human-readable \\\n  --exclude=\".git\/\" \\\n  --exclude=\".github\/\" \\\n  --exclude=\".gitignore\" \\\n  --exclude=\"README.md\" \\\n  .\/ \\\n  user@server:\/path\/to\/theme\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Mehrere Optionen verbessern dabei nicht das Deployment selbst, sondern dessen Beobachtbarkeit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>--itemize-changes<\/code> zeigt die einzelnen \u00c4nderungen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>--stats<\/code> liefert Statistiken.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>--human-readable<\/code> macht Gr\u00f6\u00dfen verst\u00e4ndlicher.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dadurch ist im GitHub-Actions-Log nicht nur sichtbar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Process exited with code 0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">sondern auch, was tats\u00e4chlich ver\u00e4ndert wurde.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Ein gr\u00fcnes <code>rsync<\/code> bedeutet nicht, dass die Website funktioniert<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist einer der wichtigsten Punkte der gesamten Pipeline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Angenommen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rsync\n  \u2193\nExit Code 0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dann wissen wir:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Die Dateien wurden erfolgreich synchronisiert.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Wir wissen nicht:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">WordPress funktioniert.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Ein PHP-Fehler k\u00f6nnte erst beim Ausf\u00fchren eines bestimmten Templates auftreten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine Funktion k\u00f6nnte zwar syntaktisch korrekt sein, aber zur Laufzeit scheitern.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Template k\u00f6nnte falsche Daten erwarten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine Seite k\u00f6nnte nach dem Deployment leer sein.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deshalb endet das Deployment nicht mit <code>rsync<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Smoke Tests gegen Produktion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Nach dem Deployment kann GitHub Actions die echte Website aufrufen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Beispiel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl \\\n  --silent \\\n  --show-error \\\n  --location \\\n  --output \/tmp\/homepage.html \\\n  --write-out \"%{http_code}\" \\\n  \"https:\/\/example.com\/\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dann wird der HTTP-Status gepr\u00fcft:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if &#91; \"$HTTP_CODE\" != \"200\" ]; then\n    exit 1\nfi<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dasselbe kann f\u00fcr zentrale Seiten geschehen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Homepage\nArticles\nwichtige Landingpage<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit pr\u00fcft die Pipeline erstmals nicht mehr nur Dateien.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sie pr\u00fcft das laufende System.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">HTTP 200 reicht ebenfalls nicht<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Auch das ist nur eine erste N\u00e4herung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine Website kann HTTP 200 zur\u00fcckgeben und trotzdem kaputt sein.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise k\u00f6nnte die Antwort enthalten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Fatal error<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">oder:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>There has been a critical error on this website.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Deshalb kann ein einfacher Smoke Test zus\u00e4tzlich nach bekannten Fehlermustern suchen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -Eqi \\\n  'Fatal error|Parse error|Uncaught Error|critical error'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Werden solche Muster gefunden, schl\u00e4gt die Pipeline fehl.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zus\u00e4tzlich kann erwartetes Markup kontrolliert werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -qi \"&lt;html\" \/tmp\/homepage.html\ngrep -qi \"&lt;body\" \/tmp\/homepage.html<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Auch das beweist nicht, dass die Website vollst\u00e4ndig korrekt funktioniert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist nicht der Anspruch eines Smoke Tests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der Smoke Test beantwortet eine kleinere Frage:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Ist das System nach dem Deployment grunds\u00e4tzlich noch am Leben?<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Validierung, Tests und Monitoring sind nicht dasselbe<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Die Pipeline enth\u00e4lt inzwischen mehrere Schutzebenen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Static Validation\n        \u2502\n        \u2502 PHP syntaktisch korrekt?\n        \u2502 JS syntaktisch korrekt?\n        \u2502 Theme vollst\u00e4ndig?\n        \u25bc\nDeployment Guards\n        \u2502\n        \u2502 Zielpfad korrekt?\n        \u2502 \u00c4nderungen plausibel?\n        \u25bc\nBackup\n        \u2502\n        \u25bc\nDeployment\n        \u2502\n        \u25bc\nSmoke Tests\n        \u2502\n        \u2502 Server erreichbar?\n        \u2502 HTTP 200?\n        \u2502 HTML vorhanden?\n        \u2502 Fatal Error?\n        \u25bc\nProduction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Jede Ebene beantwortet andere Fragen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keine davon macht die anderen \u00fcberfl\u00fcssig.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Deployment-Feedback ist Teil des Systems<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Deployment-Workflow sollte nicht nur funktionieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Er sollte verst\u00e4ndlich erkl\u00e4ren, was passiert ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine minimale Action zeigt vielleicht:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Validate       \u2713\nDeploy         \u2713<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist besser als nichts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es fehlen aber wichtige Informationen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine aussagekr\u00e4ftigere Zusammenfassung k\u00f6nnte so aussehen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Production deployment succeeded\n\nTheme               my-theme\nPrevious version    1.3.1\nDeployed version    1.4.0\nCommit              41ef712\nPlanned changes     17\nPlanned deletions   4\nBackup              Created\nBackups remaining   10\nHomepage            HTTP 200\nArticles            HTTP 200\nFatal-error scan    Passed<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions stellt daf\u00fcr <code>GITHUB_STEP_SUMMARY<\/code> bereit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    echo \"## Production deployment succeeded\"\n    echo \"\"\n    echo \"| Item | Result |\"\n    echo \"|---|---|\"\n    echo \"| Homepage | HTTP 200 |\"\n    echo \"| Backup | Created |\"\n    echo \"| Fatal-error scan | Passed |\"\n} &gt;&gt; \"$GITHUB_STEP_SUMMARY\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit wird die Pipeline selbst zu einem kleinen Deployment-Protokoll.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Die Theme-Version mitf\u00fchren<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">WordPress-Themes besitzen in <code>style.css<\/code> bereits Metadaten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/*\nTheme Name: My Theme\nVersion: 1.4.0\n*\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Pipeline kann diese Version auslesen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VERSION=$(sed -n \\\n  's\/^&#91;&#91;:space:]]*Version:&#91;&#91;:space:]]*\/\/p' \\\n  style.css \\\n  | head -1)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vor dem Deployment l\u00e4sst sich die produktive Version auslesen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Danach die neue.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das Ergebnis:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Previous version: 1.3.1\nDeployed version: 1.4.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit wird die Theme-Version Teil des Deployment-Prozesses statt nur eine dekorative Angabe in WordPress.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Was geh\u00f6rt nicht auf den Server?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Das Repository enth\u00e4lt m\u00f6glicherweise Dateien, die zur Entwicklung geh\u00f6ren, aber nicht zum Theme.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beispielsweise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.git\/\n.github\/\n.gitignore\nREADME.md\n*.zip\n*.tmp\n*.bak<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Diese k\u00f6nnen beim Deployment ausgeschlossen werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>--exclude=\".git\/\"\n--exclude=\".github\/\"\n--exclude=\".gitignore\"\n--exclude=\"README.md\"\n--exclude=\"*.zip\"\n--exclude=\"*.tmp\"\n--exclude=\"*.bak\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit wird aus dem Repository gezielt das Produktionsartefakt erzeugt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bei komplexeren Projekten w\u00e4re ein expliziter Build-Schritt noch sauberer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr ein klassisches PHP-WordPress-Theme reicht eine kontrollierte Synchronisation h\u00e4ufig aus.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\"><code>main<\/code> automatisch deployen oder Releases verwenden?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Push auf <code>main<\/code> kann unmittelbar Produktion aktualisieren:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>on:\n  push:\n    branches:\n      - main<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist echtes Continuous Deployment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der Ablauf:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Commit\n  \u2193\nPush main\n  \u2193\nValidation\n  \u2193\nDeployment\n  \u2193\nProduction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Eine konservativere Variante verwendet Git-Tags:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>on:\n  push:\n    tags:\n      - \"v*\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dann:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git tag v1.4.0\ngit push origin v1.4.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Tag wird zum expliziten Release-Signal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beide Modelle sind legitim.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die Entscheidung h\u00e4ngt davon ab, welche Bedeutung <code>main<\/code> im Entwicklungsprozess hat.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Noch eine Stufe weiter: GitHub Environments<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub unterst\u00fctzt Environments wie:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>development\nstaging\nproduction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Deployment-Job kann explizit einem Environment zugeordnet werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>environment:\n  name: production\n  url: https:\/\/example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Darauf lassen sich weitere Regeln aufbauen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bei gr\u00f6\u00dferen Systemen entsteht daraus beispielsweise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Pull Request\n     \u2502\n     \u25bc\nCI\n     \u2502\n     \u25bc\nMerge\n     \u2502\n     \u25bc\nStaging\n     \u2502\n     \u25bc\nApproval\n     \u2502\n     \u25bc\nProduction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr einen pers\u00f6nlichen WordPress-Blog kann das \u00fcberdimensioniert sein.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das zugrunde liegende Konzept bleibt trotzdem wichtig:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Deployment-Ziele sind eigene Umgebungen mit eigenen Regeln und Secrets.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Was diese Pipeline nicht l\u00f6st<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Automatisierung sollte nicht mit Sicherheit verwechselt werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die beschriebene Pipeline sch\u00fctzt beispielsweise nicht automatisch vor:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>semantischen PHP-Fehlern<\/li>\n\n\n\n<li>fehlerhaftem CSS<\/li>\n\n\n\n<li>kaputtem Responsive Design<\/li>\n\n\n\n<li>JavaScript-Logikfehlern<\/li>\n\n\n\n<li>falschen WordPress Queries<\/li>\n\n\n\n<li>Datenbankproblemen<\/li>\n\n\n\n<li>Plugin-Inkompatibilit\u00e4ten<\/li>\n\n\n\n<li>\u00c4nderungen an WordPress-Inhalten<\/li>\n\n\n\n<li>defekten externen Diensten<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Theme kann alle Syntaxchecks bestehen und trotzdem visuell zerst\u00f6rt sein.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Daf\u00fcr w\u00e4ren weitere Ebenen notwendig:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Unit Tests\nIntegration Tests\nBrowser Tests\nVisual Regression Tests\nStaging\nMonitoring<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Eine Pipeline muss nicht alles pr\u00fcfen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sie sollte aber klar definieren, was ein gr\u00fcner Status bedeutet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem Fall bedeutet er ungef\u00e4hr:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Der erwartete Theme-Code ist vollst\u00e4ndig, syntaktisch valide, wurde auf das richtige Ziel synchronisiert und die wichtigsten WordPress-Seiten antworten danach ohne offensichtlichen Laufzeitfehler.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist bereits erheblich st\u00e4rker als:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Der Upload ist fertig.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Der vollst\u00e4ndige Denkprozess<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Am Anfang steht h\u00e4ufig dieses Modell:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ich habe ein WordPress-Theme.\n\nIch \u00e4ndere Dateien.\n\nDann lade ich sie hoch.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das f\u00fchrt fast automatisch zu einem dateizentrierten Prozess.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das bessere Modell lautet:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ich entwickle Software.\n\nDas Repository beschreibt einen definierten Zustand.\n\nDieser Zustand wird validiert.\n\nEin validierter Zustand wird deployt.\n\nDer vorherige Zustand wird gesichert.\n\nDas Deployment wird anschlie\u00dfend verifiziert.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Daraus entsteht:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Git Repository\n      \u2502\n      \u2502 Source of Truth\n      \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   Validation    \u2502\n\u2502                 \u2502\n\u2502 PHP             \u2502\n\u2502 JavaScript      \u2502\n\u2502 Theme structure \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n         \u2502\n         \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Deployment Guard\u2502\n\u2502                 \u2502\n\u2502 SSH             \u2502\n\u2502 Target check    \u2502\n\u2502 rsync dry-run   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n         \u2502\n         \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502     Backup      \u2502\n\u2502                 \u2502\n\u2502 Timestamp       \u2502\n\u2502 Commit          \u2502\n\u2502 Retention       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n         \u2502\n         \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502    Deployment   \u2502\n\u2502                 \u2502\n\u2502 rsync           \u2502\n\u2502 --delete        \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n         \u2502\n         \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  Verification   \u2502\n\u2502                 \u2502\n\u2502 HTTP            \u2502\n\u2502 HTML            \u2502\n\u2502 Fatal errors    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n         \u2502\n         \u25bc\n     Production<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions ist dabei nicht der entscheidende Teil.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auch GitLab CI\/CD, Jenkins, Azure DevOps oder ein anderes System k\u00f6nnten denselben Prozess abbilden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die entscheidende Ver\u00e4nderung ist die Architektur des Entwicklungsprozesses.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Vom Theme-Upload zum Software-Deployment<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Der Unterschied zwischen beiden Ans\u00e4tzen l\u00e4sst sich auf wenige Zeilen reduzieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vorher:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Dateien \u00e4ndern\n    \u2193\nDateien hochladen\n    \u2193\nfertig<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nachher:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u00c4nderung\n    \u2193\nGit\n    \u2193\nValidierung\n    \u2193\nDry Run\n    \u2193\nBackup\n    \u2193\nDeployment\n    \u2193\nSmoke Test\n    \u2193\nProduction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das wirkt f\u00fcr ein WordPress-Theme zun\u00e4chst nach viel Infrastruktur.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die einzelnen Bausteine sind jedoch klein.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein PHP-Syntaxcheck kostet fast nichts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein SSH-Verbindungstest kostet fast nichts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein <code>rsync --dry-run<\/code> kostet fast nichts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Backup vor dem \u00dcberschreiben kostet wenig.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein HTTP-Smoke-Test kostet wenige Sekunden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zusammen ver\u00e4ndern diese kleinen Guardrails die Eigenschaft des gesamten Prozesses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das Theme liegt nicht mehr einfach auf einem Webserver.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es besitzt einen nachvollziehbaren Lebenszyklus.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Und genau das sollte f\u00fcr ein WordPress-Theme genauso selbstverst\u00e4ndlich sein wie f\u00fcr jede andere Software.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ein selbst entwickeltes WordPress-Theme ist Software. Es enth\u00e4lt PHP, JavaScript, CSS, Templates, Assets und Konfiguration. \u00c4nderungen an einer Datei k\u00f6nnen Auswirkungen auf andere Teile des Systems haben. Fehler k\u00f6nnen die komplette Website unbrauchbar machen. Trotzdem werden WordPress-Themes erstaunlich oft anders behandelt als andere Software: Oder noch direkter: F\u00fcr kleine \u00c4nderungen funktioniert das erstaunlich lange. Bis [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":392,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[60,57,25,58,59],"tags":[],"class_list":["post-391","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-automation","category-infrastructure","category-software-engineering","category-ssh-remote-access","category-system-engineering"],"_links":{"self":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/391","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=391"}],"version-history":[{"count":1,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/391\/revisions"}],"predecessor-version":[{"id":393,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/391\/revisions\/393"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/media\/392"}],"wp:attachment":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=391"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=391"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=391"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}