Mit Visual Studio und GitHub eine komplette CI/CD-Pipeline für .NET erstellen

Wer Software entwickelt, kennt den klassischen Ablauf: Code ändern, Projekt kompilieren, Tests durchführen und anschließend die neue Version auf einen Server übertragen. Solange ein Projekt klein ist, kann man diese Schritte problemlos manuell durchführen. Mit zunehmender Größe und häufigeren Änderungen wird dieser Prozess jedoch schnell fehleranfällig.

Mit Visual Studio, GitHub und GitHub Actions lässt sich dieser Ablauf vollständig automatisieren. Nach einem Push prüft GitHub automatisch, ob die Anwendung kompiliert und alle Tests erfolgreich sind. Ist alles in Ordnung, kann die neue Version anschließend automatisch auf einem Server oder bei einem Cloud-Anbieter bereitgestellt werden.

Genau das bezeichnet man als CI/CD-Pipeline.

In diesem Beitrag erstellen wir beispielhaft eine Pipeline für eine ASP.NET-Core-Webanwendung.


Was bedeutet CI/CD?

CI/CD steht für:

Continuous Integration (CI)
und
Continuous Delivery beziehungsweise Continuous Deployment (CD).

Continuous Integration

Bei Continuous Integration wird jede Änderung automatisch geprüft.

Ein typischer Ablauf sieht so aus:

Entwickler ändert Code
        ↓
Commit
        ↓
Push zu GitHub
        ↓
Projekt kompilieren
        ↓
Tests ausführen
        ↓
Ergebnis anzeigen

Dadurch erkennt man beispielsweise sofort, wenn eine Änderung dazu führt, dass das Projekt nicht mehr kompiliert oder ein automatisierter Test fehlschlägt.

Continuous Deployment

Continuous Deployment geht noch einen Schritt weiter.

Nachdem Build und Tests erfolgreich abgeschlossen wurden, wird die neue Version automatisch veröffentlicht:

Push
 ↓
Build
 ↓
Tests
 ↓
Publish
 ↓
Deployment
 ↓
Neue Version läuft

Damit kann eine Änderung wenige Minuten nach dem Push automatisch auf dem Zielsystem verfügbar sein.


Was benötigen wir?

Für unser Beispiel benötigen wir:

  • Visual Studio
  • ein .NET-Projekt
  • ein GitHub-Konto
  • ein GitHub-Repository
  • automatisierte Tests
  • GitHub Actions
  • ein Ziel für das Deployment

Als Beispiel für das Deployment verwenden wir einen Azure App Service. Das grundsätzliche Prinzip ist jedoch auch auf Linux-Server, Docker-Hosts oder andere Cloud-Plattformen übertragbar.


1. Das Projekt in Visual Studio erstellen

Zunächst benötigen wir ein normales .NET-Projekt.

Beispielsweise:

MyWebApp
│
├── MyWebApp
│   ├── Program.cs
│   ├── appsettings.json
│   └── ...
│
└── MyWebApp.Tests
    └── ...

Zusätzlich zum eigentlichen Projekt sollte möglichst bereits ein Testprojekt vorhanden sein.

In Visual Studio könnte die Solution beispielsweise so aussehen:

Solution 'MyWebApp'
│
├── MyWebApp
│
└── MyWebApp.Tests

Damit können wir später automatisiert überprüfen, ob die Anwendung weiterhin korrekt funktioniert.


2. Das Projekt mit Git verbinden

Visual Studio besitzt eine integrierte Git-Unterstützung.

Ist das Projekt noch kein Git-Repository, kann über Visual Studio ein neues Repository angelegt werden.

Anschließend werden die Dateien über einen Commit in Git gespeichert.

Zum Beispiel:

Initial commit

Technisch entspricht das ungefähr:

git add .
git commit -m "Initial commit"

Von nun an werden Änderungen über Git versioniert.


3. Repository auf GitHub erstellen

Als Nächstes wird das lokale Repository mit GitHub verbunden.

Visual Studio kann direkt ein neues GitHub-Repository erstellen und das vorhandene Projekt hochladen.

Danach sieht die Struktur beispielsweise so aus:

Visual Studio
     │
     │ Commit
     ▼
lokales Git-Repository
     │
     │ Push
     ▼
GitHub Repository

GitHub wird damit zur zentralen Stelle für unseren Quellcode.


4. Den ersten CI-Workflow erstellen

Jetzt beginnt die eigentliche Automatisierung.

GitHub Actions sucht Workflow-Dateien im Verzeichnis:

.github/workflows/

Wir erstellen dort beispielsweise:

.github/workflows/ci.yml

Ein einfacher Workflow für ein aktuelles .NET-Projekt könnte folgendermaßen aussehen:

name: CI

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

jobs:

  build-and-test:

    runs-on: ubuntu-latest

    steps:

      - name: Repository herunterladen
        uses: actions/checkout@v6

      - name: .NET installieren
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Abhängigkeiten wiederherstellen
        run: dotnet restore

      - name: Projekt bauen
        run: dotnet build --no-restore --configuration Release

      - name: Tests ausführen
        run: dotnet test --no-build --configuration Release

Damit haben wir bereits eine einfache CI-Pipeline.


Was passiert jetzt?

Sobald wir eine Änderung auf den Branch main pushen, startet GitHub automatisch den Workflow.

Push auf main
      │
      ▼
GitHub Actions
      │
      ▼
Repository herunterladen
      │
      ▼
.NET installieren
      │
      ▼
dotnet restore
      │
      ▼
dotnet build
      │
      ▼
dotnet test
      │
      ▼
✅ erfolgreich
oder
❌ fehlgeschlagen

Der Entwickler muss Build und Tests damit nicht mehr ausschließlich lokal durchführen.

GitHub überprüft den Stand des Repositorys noch einmal unabhängig auf einem frischen Runner.


5. Was ist ein GitHub Runner?

Die Befehle des Workflows müssen irgendwo ausgeführt werden.

Dafür verwendet GitHub sogenannte Runner.

In unserem Workflow steht:

runs-on: ubuntu-latest

GitHub startet dafür eine Linux-Umgebung und führt dort unseren Workflow aus.

Alternativ stehen beispielsweise auch Windows und macOS zur Verfügung:

runs-on: windows-latest

oder:

runs-on: macos-latest

Für viele ASP.NET-Core-Anwendungen reicht ein Ubuntu-Runner vollkommen aus.


6. Pull Requests automatisch überprüfen

Die CI-Pipeline ist besonders praktisch in Verbindung mit Pull Requests.

Angenommen, wir entwickeln ein neues Feature auf einem eigenen Branch:

main
 │
 └── feature/login

Nach der Entwicklung erstellen wir einen Pull Request:

feature/login
      ↓
Pull Request
      ↓
main

Da unser Workflow auf pull_request reagiert, führt GitHub automatisch Build und Tests durch.

Pull Request
      │
      ├── Build ✅
      │
      └── Tests ✅
      │
      ▼
Merge möglich

Schlägt dagegen ein Test fehl:

Pull Request
      │
      ├── Build ✅
      │
      └── Tests ❌

sollte die Änderung zunächst korrigiert werden.


7. Branch Protection einrichten

GitHub kann zusätzlich verhindern, dass fehlerhafter Code direkt in main übernommen wird.

Dazu können für den Hauptbranch Schutzregeln eingerichtet werden.

Beispielsweise:

  • Pull Request erforderlich
  • erfolgreicher CI-Workflow erforderlich
  • bestimmte Reviews erforderlich
  • direkte Pushes auf main verhindern

Damit entsteht ein kontrollierter Entwicklungsprozess:

Feature Branch
      │
      ▼
Pull Request
      │
      ▼
CI
 ┌────┴────┐
 │         │
Build     Tests
 │         │
 └────┬────┘
      │
      ▼
Review
      │
      ▼
Merge
      │
      ▼
main

8. Aus CI wird CI/CD

Bis jetzt überprüft GitHub unsere Anwendung lediglich.

Nun soll sie nach einem erfolgreichen Merge auf main zusätzlich veröffentlicht werden.

Dazu erweitern wir die Pipeline um einen Deployment-Schritt.

Unser Ablauf sieht dann so aus:

Visual Studio
      │
      ▼
Git Commit
      │
      ▼
GitHub
      │
      ▼
GitHub Actions
      │
      ├── Restore
      ├── Build
      ├── Test
      ├── Publish
      │
      ▼
Deployment
      │
      ▼
Produktionsserver

9. Anwendung mit dotnet publish vorbereiten

Für das Deployment erzeugen wir zunächst eine veröffentlichbare Version unserer Anwendung.

Dafür verwenden wir:

dotnet publish --configuration Release --output ./publish

Der Inhalt des Ordners publish enthält anschließend die Dateien, die auf dem Zielsystem benötigt werden.

Im Workflow sieht das so aus:

- name: Anwendung veröffentlichen
  run: dotnet publish ./MyWebApp/MyWebApp.csproj --configuration Release --output ./publish

10. Zugangsdaten nicht im Workflow speichern

Für das Deployment benötigt GitHub Zugang zum Zielsystem.

Passwörter, API-Schlüssel oder andere Zugangsdaten dürfen niemals direkt in der Workflow-Datei gespeichert werden.

Also nicht:

password: MeinGeheimesPasswort123

GitHub stellt dafür Secrets zur Verfügung.

Diese können in den Einstellungen des Repositorys hinterlegt und anschließend im Workflow verwendet werden.

Beispielsweise:

${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}

Der eigentliche Inhalt des Secrets steht damit nicht im Repository.


11. Deployment nach Azure App Service

Für eine ASP.NET-Core-Webanwendung bietet sich beispielsweise Azure App Service an.

Nachdem dort eine Web App angelegt wurde, können die benötigten Deployment-Zugangsdaten als GitHub Secret gespeichert werden.

Der Deployment-Step könnte dann beispielsweise so aussehen:

- name: Anwendung deployen
  uses: azure/webapps-deploy@v3
  with:
    app-name: meine-webapp
    publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
    package: ./publish

GitHub überträgt damit die zuvor erzeugte Anwendung automatisch zu Azure.


12. Der komplette CI/CD-Workflow

Unsere Workflow-Datei könnte jetzt folgendermaßen aussehen:

name: CI-CD

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

jobs:

  build-test:

    runs-on: ubuntu-latest

    steps:

      - name: Repository herunterladen
        uses: actions/checkout@v6

      - name: .NET installieren
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Abhängigkeiten wiederherstellen
        run: dotnet restore

      - name: Projekt bauen
        run: dotnet build --no-restore --configuration Release

      - name: Tests ausführen
        run: dotnet test --no-build --configuration Release

  deploy:

    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    needs:
      - build-test

    runs-on: ubuntu-latest

    environment:
      name: production

    steps:

      - name: Repository herunterladen
        uses: actions/checkout@v6

      - name: .NET installieren
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Anwendung veröffentlichen
        run: dotnet publish ./MyWebApp/MyWebApp.csproj --configuration Release --output ./publish

      - name: Anwendung deployen
        uses: azure/webapps-deploy@v3
        with:
          app-name: meine-webapp
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
          package: ./publish

Damit besitzen wir bereits eine vollständige CI/CD-Pipeline.


Warum gibt es zwei Jobs?

Der Workflow enthält:

build-test

und:

deploy

Das ist bewusst so gewählt.

Der Deployment-Job besitzt folgende Abhängigkeit:

needs:
  - build-test

Das bedeutet:

Deployment darf erst beginnen, wenn Build und Tests erfolgreich abgeschlossen wurden.

Schlägt beispielsweise ein Test fehl:

Build ✅
Tests ❌
   │
   X
Deployment

wird keine neue Version veröffentlicht.

Erst bei:

Build ✅
Tests ✅
   │
   ▼
Deployment

wird die Anwendung bereitgestellt.


13. Pull Requests dürfen nicht automatisch deployen

Unsere Pipeline wird sowohl bei Pushes als auch bei Pull Requests gestartet.

Wir möchten allerdings nicht jeden Pull Request sofort produktiv veröffentlichen.

Deshalb besitzt der Deployment-Job diese Bedingung:

if: github.event_name == 'push' && github.ref == 'refs/heads/main'

Damit läuft das Deployment ausschließlich bei einem echten Push auf main.

Der Ablauf eines Pull Requests sieht also so aus:

Pull Request
     ↓
Build
     ↓
Tests
     ↓
Ende

Nach dem Merge:

Merge nach main
       ↓
Push auf main
       ↓
Build
       ↓
Tests
       ↓
Publish
       ↓
Deploy

14. GitHub Environments verwenden

Für Deployments bietet GitHub sogenannte Environments an.

Typische Umgebungen wären:

development
staging
production

Unser Deployment verwendet beispielsweise:

environment:
  name: production

Ein Environment kann zusätzliche Regeln besitzen.

Zum Beispiel:

  • Deployment nur von main
  • manuelle Freigabe erforderlich
  • eigene Secrets
  • Deployment-Historie
  • Schutzregeln

Damit lässt sich ein wesentlich sichererer Deployment-Prozess aufbauen.


15. Manuelle Freigabe für Produktion

Eine interessante Variante besteht darin, Build und Tests vollständig automatisch auszuführen, das tatsächliche Produktionsdeployment aber zunächst freigeben zu lassen.

Der Ablauf wäre:

Push auf main
      ↓
Build
      ↓
Tests
      ↓
Publish
      ↓
Warte auf Freigabe
      ↓
   [Approve]
      ↓
Deployment

Damit erhält man die Vorteile der Automatisierung, behält aber die Kontrolle darüber, wann eine neue Version tatsächlich produktiv geht.


16. Staging und Production

Noch professioneller ist eine zusätzliche Staging-Umgebung.

Der Ablauf könnte dann folgendermaßen aussehen:

Feature Branch
      ↓
Pull Request
      ↓
Build + Tests
      ↓
Merge nach main
      ↓
Build
      ↓
Tests
      ↓
Deploy Staging
      ↓
Integrationstest
      ↓
Freigabe
      ↓
Deploy Production

Dadurch kann eine Version zunächst in einer produktionsähnlichen Umgebung getestet werden.

Erst anschließend wird sie für die eigentlichen Benutzer veröffentlicht.


17. Build-Artefakte verwenden

Bei größeren Pipelines empfiehlt es sich, die Anwendung nur einmal zu bauen.

Das Ergebnis kann als sogenanntes Artifact gespeichert werden.

Beispielsweise:

- name: Build-Artefakt hochladen
  uses: actions/upload-artifact@v4
  with:
    name: webapp
    path: ./publish

Ein späterer Deployment-Job kann dieses Artefakt wieder herunterladen.

Damit wird exakt die Version deployt, die zuvor getestet wurde.

Der Ablauf sieht dann so aus:

Quellcode
   ↓
Build
   ↓
Tests
   ↓
Artifact
   ↓
Staging
   ↓
Production

Dieses Vorgehen ist für größere CI/CD-Pipelines empfehlenswert.


18. Wo endet Visual Studio und wo beginnt GitHub?

Visual Studio ist in diesem Prozess hauptsächlich die Entwicklungsumgebung.

Hier wird:

  • programmiert
  • getestet
  • committed
  • gepusht
  • mit Branches gearbeitet
  • ein Pull Request vorbereitet

GitHub übernimmt anschließend die zentrale Automatisierung.

┌───────────────────────┐
│     Visual Studio     │
│                       │
│ Code                  │
│ lokale Tests          │
│ Git                   │
│ Commit                │
│ Push                  │
└───────────┬───────────┘
            │
            ▼
┌───────────────────────┐
│        GitHub         │
│                       │
│ Repository            │
│ Pull Requests         │
│ Reviews               │
│ GitHub Actions        │
└───────────┬───────────┘
            │
            ▼
┌───────────────────────┐
│       CI/CD           │
│                       │
│ Restore               │
│ Build                 │
│ Test                  │
│ Publish               │
│ Deploy                │
└───────────┬───────────┘
            │
            ▼
┌───────────────────────┐
│   Produktionssystem   │
└───────────────────────┘

19. Der tägliche Entwicklungsablauf

Ist die Pipeline einmal eingerichtet, verändert sie den normalen Entwicklungsprozess kaum.

Ein typischer Ablauf sieht dann so aus:

1. Feature-Branch erstellen

feature/neue-funktion

2. In Visual Studio entwickeln

Code schreiben und lokal testen.

3. Commit erstellen

Beispielsweise:

Add customer search

4. Zu GitHub pushen

Visual Studio
    ↓
GitHub

5. Pull Request erstellen

GitHub startet automatisch die CI-Pipeline.

6. Build und Tests überprüfen

Build ✅
Tests ✅

7. Pull Request mergen

Der Code landet auf main.

8. CD-Pipeline startet

Build
 ↓
Test
 ↓
Publish
 ↓
Deploy

9. Neue Version ist verfügbar

Damit ist der komplette Prozess abgeschlossen.


20. Was sollte nicht automatisch deployt werden?

Nicht jedes Projekt benötigt sofort Continuous Deployment.

Bei geschäftskritischer Software kann beispielsweise sinnvoll sein:

Continuous Integration
        +
Continuous Delivery

statt:

Continuous Deployment

Der Unterschied liegt darin, dass bei Continuous Delivery die Anwendung automatisch bis zur Veröffentlichungsreife vorbereitet wird, das eigentliche Deployment aber bewusst freigegeben werden muss.

Gerade für Produktionssysteme ist das häufig eine sinnvolle Lösung.


21. Sicherheit nicht vergessen

Eine CI/CD-Pipeline besitzt Zugriff auf wichtige Systeme und sollte entsprechend abgesichert werden.

Einige grundlegende Regeln sind:

  • Passwörter niemals im Repository speichern
  • GitHub Secrets verwenden
  • Produktionszugänge möglichst auf das Notwendige beschränken
  • Branch Protection beziehungsweise Rulesets verwenden
  • Pull Requests vor dem Merge prüfen
  • Deployment-Environments einsetzen
  • Produktionsdeployments gegebenenfalls manuell freigeben
  • verwendete GitHub Actions regelmäßig aktualisieren

Für professionelle Azure-Deployments kann außerdem OpenID Connect (OIDC) verwendet werden. Dadurch lassen sich langlebige Zugangsdaten teilweise vermeiden.


22. CI/CD ist mehr als nur automatisches Deployment

Der größte Vorteil einer Pipeline besteht nicht darin, dass man sich das manuelle Kopieren von Dateien spart.

Der entscheidende Vorteil ist ein reproduzierbarer Prozess.

Jede Version durchläuft dieselben Schritte:

Restore
   ↓
Build
   ↓
Test
   ↓
Publish
   ↓
Deploy

Dadurch hängt die Qualität einer Veröffentlichung nicht mehr davon ab, ob ein Entwickler daran gedacht hat, bestimmte Schritte manuell auszuführen.


Fazit

Mit Visual Studio, GitHub und GitHub Actions lässt sich mit relativ wenig Aufwand eine vollständige CI/CD-Pipeline für .NET-Anwendungen aufbauen.

Visual Studio übernimmt dabei die lokale Entwicklung und Git-Verwaltung. GitHub speichert den zentralen Quellcode und organisiert Branches und Pull Requests. GitHub Actions führt anschließend automatisiert Build, Tests und Deployment durch.

Aus:

Code schreiben
↓
manuell bauen
↓
manuell testen
↓
Dateien manuell übertragen

wird damit:

Code schreiben
      ↓
Commit & Push
      ↓
GitHub Actions
      ↓
Build
      ↓
Tests
      ↓
Deployment

Für den Einstieg reicht bereits eine kleine CI-Pipeline aus, die bei jedem Pull Request automatisch dotnet build und dotnet test ausführt.

Im nächsten Schritt kann daraus eine vollständige Pipeline mit Artifacts, Staging-Umgebung, manueller Produktionsfreigabe und automatischem Deployment entstehen.

Damit wird GitHub von einer reinen Quellcodeverwaltung zu einem zentralen Bestandteil des gesamten Entwicklungs- und Veröffentlichungsprozesses.

Mit Visual Studio und GitHub eine komplette CI/CD-Pipeline für .NET erstellen
Markiert in:                 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


CAPTCHA-Bild
Bild neu laden