TL;DR — Kurzzusammenfassung

GitHub Actions Deployment-Guide: automatisierte Pipelines mit Umgebungsschutz, Secrets, mehreren Deploy-Zielen, Caching und Rollback-Strategien erstellen.

AUTOMATISIERTE DEPLOYMENT-PIPELINE — GITHUB ACTIONS git push Auslöser Lint + Test Qualität Build Artefakte Genehmigung Gate Deploy Produktion Benachr. Slack push → lint → test → build → genehmigen → deployen → benachrichtigen

Manuelles Deployen — per SSH auf einen Server verbinden, Build-Befehle ausführen, die Daumen drücken — ist fehleranfällig, nicht nachvollziehbar und skaliert nicht. GitHub Actions ermöglicht es, die gesamte Deployment-Pipeline als Code zu definieren: Lint, Tests, Build, Genehmigung, Deployment und Benachrichtigung — alles automatisch ausgelöst bei jedem Push auf main. Dieser Leitfaden deckt die Erstellung eines produktionstauglichen automatisierten Deployment-Workflows ab, vom ersten YAML-File bis zu Multi-Target-Deployments, Umgebungsschutz und Rollback-Strategien.

Voraussetzungen

Bevor Sie Ihren Deployment-Workflow erstellen, stellen Sie sicher, dass Folgendes vorhanden ist:

  • Ein GitHub-Repository mit Anwendungscode (Node.js-Beispiele im gesamten Leitfaden)
  • Eine Zielumgebung — Cloudflare-Workers-Konto, ein VPS mit SSH-Zugang oder ein selbst-gehosteter Windows-Runner für IIS
  • YAML-Grundkenntnisse — GitHub-Actions-Workflows sind YAML-Dateien, bei denen Einrückungen wichtig sind
  • Zugriff auf die Repository-Einstellungen in GitHub zum Speichern von Secrets und Erstellen von Umgebungen

GitHub Actions Grundlagen

Ein GitHub-Actions-Workflow ist eine YAML-Datei, die in .github/workflows/ gespeichert ist. Er wird durch ein oder mehrere Ereignisse ausgelöst (Push, Pull Request, Zeitplan, manueller Auslöser). Ein Workflow enthält einen oder mehrere Jobs. Jeder Job läuft auf einem Runner — einer virtuellen Maschine, die von GitHub gehostet wird (Ubuntu, Windows, macOS) oder selbst gehostet ist. Jeder Job enthält sequentielle Steps, von denen jeder entweder einen Shell-Befehl (run) oder eine wiederverwendbare Einheit namens Action (uses) ausführt.

Die grundlegenden Bausteine:

name: Deploy                      # Workflow-Name im Actions-Tab

on:                               # Auslöser
  push:
    branches: [main]

jobs:
  deploy:                         # Job-ID
    runs-on: ubuntu-latest        # Runner
    steps:
      - uses: actions/checkout@v4 # Action (lädt Quellcode herunter)
      - run: npm ci               # Shell-Befehl
      - run: npm run build

Jobs laufen standardmäßig parallel. Verwenden Sie needs: [job-id], um eine Reihenfolge zu erzwingen.

Ihr Erster Deployment-Workflow

Hier ist ein vollständiger Workflow für ein Node.js-Projekt, das auf Cloudflare Workers mit Wrangler deployt. Er führt zunächst Lint und Tests durch, dann den Build und zuletzt das Deployment — nur bei Pushes auf main.

name: Auf Cloudflare Workers deployen

on:
  push:
    branches: [main]

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run lint
      - run: npm test

  build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - name: Mit Wrangler deployen
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
      - name: Erfolg in Slack melden
        if: success()
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -H "Content-Type: application/json" \
            -d "{\"text\":\"${{ github.sha }} von ${{ github.actor }} in Produktion deployt\"}"

Der concurrency-Block oben verhindert, dass zwei Deployments sich überlappen. Wenn Sie zweimal schnell hintereinander pushen, wird die erste Ausführung abgebrochen, bevor die zweite beginnt.

Umgebungsschutzregeln

Direkt in die Produktion zu pushen ohne menschliche Überprüfung ist für kritische Systeme riskant. GitHub-Umgebungen ermöglichen es, Schutzregeln zu jedem Job hinzuzufügen, der sie anvisiert.

So konfigurieren Sie den Umgebungsschutz:

  1. Gehen Sie zu Settings → Environments in Ihrem Repository
  2. Klicken Sie auf New environment und nennen Sie es production
  3. Aktivieren Sie Required reviewers — fügen Sie einen oder mehrere GitHub-Benutzer oder -Teams hinzu
  4. Legen Sie optional einen Wait timer fest (z. B. 10 Minuten), bevor der Job ausgeführt werden darf
  5. Beschränken Sie optional, welche Branches in diese Umgebung deployen können (z. B. nur main)

Einmal konfiguriert, pausiert jeder Job mit environment: production und wartet auf die Genehmigung eines Reviewers, bevor er fortfährt. Der Reviewer sieht das ausstehende Deployment in der GitHub-Oberfläche und kann es mit einem Kommentar genehmigen oder ablehnen.

Verwaltung von Secrets

Codieren Sie niemals API-Tokens, SSH-Schlüssel oder Passwörter fest in Workflow-Dateien. GitHub bietet drei Ebenen der Secret-Speicherung:

Repository-Secrets — Für alle Workflows im Repo verfügbar. Gehen Sie zu Settings → Secrets and variables → Actions → New repository secret.

Umgebungs-Secrets — Auf eine bestimmte Umgebung (z. B. production) beschränkt. Werden nur exponiert, wenn ein Job auf diese Umgebung abzielt.

Organisations-Secrets — Zwischen mehreren Repositories einer GitHub-Organisation geteilt, ideal für Tokens, die von vielen Projekten genutzt werden.

Referenzieren Sie Secrets in Workflows mit der Syntax ${{ secrets.SECRET_NAME }}:

- name: Mit Wrangler deployen
  uses: cloudflare/wrangler-action@v3
  with:
    apiToken: ${{ secrets.CF_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

OIDC (OpenID Connect) ist eine Alternative zu langlebigen Secrets für Cloud-Anbieter, die es unterstützen (AWS, GCP, Azure). Anstatt ein statisches Token zu speichern, fordert der Runner zur Laufzeit kurzlebige Credentials vom Cloud-Anbieter an.

Deployment auf Verschiedene Ziele

Cloudflare Workers mit Wrangler

Die Action cloudflare/wrangler-action übernimmt die Authentifizierung und führt jeden Wrangler-Befehl aus:

- uses: cloudflare/wrangler-action@v3
  with:
    apiToken: ${{ secrets.CF_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    command: deploy --env production

VPS über SSH und rsync

Für einen Linux-VPS verwenden Sie SSH zur Verbindung und rsync zur Dateiübertragung:

- name: Auf VPS deployen
  uses: appleboy/ssh-action@v1
  with:
    host: ${{ secrets.DEPLOY_HOST }}
    username: ${{ secrets.DEPLOY_USER }}
    key: ${{ secrets.DEPLOY_SSH_KEY }}
    script: |
      rsync -az --delete dist/ /var/www/myapp/dist/
      systemctl restart myapp

Speichern Sie den privaten SSH-Schlüssel in GitHub Secrets. Fügen Sie den entsprechenden öffentlichen Schlüssel zu ~/.ssh/authorized_keys auf dem Server hinzu. Verwenden Sie einen dedizierten Deploy-Benutzer mit minimalen Berechtigungen — niemals root.

IIS über Selbst-Gehosteten Runner

Für Windows-IIS-Deployments führen Sie einen selbst-gehosteten Runner direkt auf dem Server aus:

deploy-iis:
  needs: build
  runs-on: [self-hosted, iis-deploy]
  environment: production
  steps:
    - uses: actions/download-artifact@v4
      with:
        name: dist
        path: C:\inetpub\wwwroot\myapp
    - name: IIS-Site neu starten
      run: |
        Import-Module WebAdministration
        Restart-WebItem 'IIS:\Sites\MyApp'
      shell: powershell

Caching und Performance

Dependency-Caching

Das erneute Herunterladen von npm-Paketen bei jeder Ausführung verschwendet Zeit. Cachen Sie sie:

- uses: actions/setup-node@v4
  with:
    node-version: "22"
    cache: "npm"

Für .NET:

- uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
    restore-keys: ${{ runner.os }}-nuget-

Concurrency-Gruppen

Concurrency-Gruppen verhindern parallele Deployments — eine häufige Ursache für Race Conditions:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Vergleich: GitHub Actions vs. GitLab CI vs. Jenkins

MerkmalGitHub ActionsGitLab CIJenkins
HostingGitHub (SaaS)GitLab oder selbst-gehostetNur selbst-gehostet
Konfigurationsdatei.github/workflows/*.yml.gitlab-ci.ymlJenkinsfile (Groovy)
Kostenloser Plan2.000 Min./Monat (privat)400 Min./Monat (privat)Unbegrenzt (eigene Infra)
Marketplace20.000+ ActionsBegrenzt1.800+ Plugins
Umgebungs-GatesNativ (kostenlos für öffentlich)Nativ (Ultimate für Regeln)Über Plugins
OIDCAWS, GCP, Azure, CloudflareAWS, GCP, AzureÜber Plugins
LernkurveNiedrigNiedrigHoch (Groovy-DSL)
Ideal fürProjekte auf GitHubMonorepos auf GitLabKomplexe Legacy-Pipelines

Praxisszenario: Vollständige Pipeline

Eine Produktions-Pipeline für Node.js mit Slack-Benachrichtigungen:

name: Produktions-Deployment

on:
  push:
    branches: [main]

concurrency:
  group: production-deploy
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm test -- --coverage

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
          retention-days: 14

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
      - name: Erfolg melden
        if: success()
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -d "{\"text\":\"${{ github.sha }} von ${{ github.actor }} deployt\"}"
      - name: Fehler melden
        if: failure()
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -d "{\"text\":\"FEHLER beim Deployment von ${{ github.sha }}\"}"

Status-Badges

Fügen Sie ein Live-Status-Badge zu Ihrer README hinzu:

![Deploy](https://github.com/ihre-org/ihr-repo/actions/workflows/deploy.yml/badge.svg)

Fallstricke und Grenzfälle

Secret-Maskierung funktioniert nur bei exakten Übereinstimmungen. GitHub maskiert den literalen Secret-Wert in den Logs, aber wenn das Secret vor der Verwendung base64- oder URL-kodiert wird, kann der Originalwert unmaskiert erscheinen.

Runner-Image-Änderungen brechen angeheftete Tool-Versionen. GitHub aktualisiert ubuntu-latest regelmäßig. Wenn Ihr Workflow auf einer bestimmten vorinstallierten Tool-Version basiert, installieren Sie sie explizit mit einer Setup-Action.

cancel-in-progress: true bricht die aktuelle Ausführung ab, nicht die eingehende. Der neue Push gewinnt. Das ist für Deployments fast immer das Gewünschte.

Umgebungs-Secrets stehen bei Fork-Pull-Requests nicht zur Verfügung. Das ist absichtlich — Forks können keine Secrets zugreifen, um Datenlecks zu verhindern.

Fehlerbehebung

Workflow wird beim Push auf main nicht ausgelöst. Überprüfen Sie den Filter on.push.branches — der Branch-Name muss exakt übereinstimmen. Bestätigen Sie, dass die Workflow-Datei in .github/workflows/ liegt.

Secret ist verfügbar, aber die Wrangler-Authentifizierung schlägt fehl. Stellen Sie sicher, dass der Secret-Name exakt übereinstimmt (Groß-/Kleinschreibung beachten). Verifizieren Sie, dass das Cloudflare-API-Token die richtigen Berechtigungen hat: Workers Scripts: Edit und Account: Read.

Job wartet unbegrenzt auf Umgebungsgenehmigung. Überprüfen Sie, dass Sie als erforderlicher Reviewer der Umgebung aufgeführt sind. Bestätigen Sie, dass der Umgebungsname im Workflow-YAML exakt mit dem Namen in den Settings übereinstimmt.

Zusammenfassung

  • Definieren Sie Workflows als YAML-Dateien in .github/workflows/ — sie sind gemeinsam mit Ihrem Code versioniert
  • Strukturieren Sie Pipelines als sequentielle Jobs: lint → test → build → deploy, mit needs zur Durchsetzung der Reihenfolge
  • Verwenden Sie environment: production mit erforderlichen Reviewern für menschliche Genehmigungstore
  • Speichern Sie alle Secrets im GitHub-Secrets-Store — niemals in der YAML-Datei selbst
  • Cloudflare-Workers-Deployments verwenden cloudflare/wrangler-action; VPS-Ziele nutzen SSH/rsync; IIS verwendet einen selbst-gehosteten Runner
  • Fügen Sie concurrency-Gruppen mit cancel-in-progress: true hinzu, um überlappende Deployments zu verhindern
  • Cachen Sie Abhängigkeiten mit actions/setup-node cache: npm für schnellere Ausführungen
  • Benennen Sie Artefakte mit ${{ github.sha }} für einfaches Rollback durch erneutes Ausführen des Workflows auf einem früheren Commit

Verwandte Artikel