diff --git a/content/2025/labs/99_shared/ci/_index.md b/content/2025/labs/99_shared/ci/_index.md
new file mode 100644
index 000000000..d5280df45
--- /dev/null
+++ b/content/2025/labs/99_shared/ci/_index.md
@@ -0,0 +1,279 @@
+---
+title: "Continuous Integration (CI) - Automatisiere deine Builds und Tests"
+linkTitle: "Continuous Integration (CI)"
+type: docs
+weight: 5
+description: >
+ In diesem Abschnitt lernst du, wie du Continuous Integration (CI) einrichtest, um Builds und Tests zu automatisieren.
+---
+
+
+
+## Voraussetzung
+
+- Du kennst die Grundlagen von Git und verstehst, wie Versionskontrollsysteme funktionieren.
+- Du kennst die Grundlagen von Java und Maven und kannst einfache Java-Projekte erstellen und verwalten.
+- Du kennst die Grundlagen des Software-Testings.
+- Du verstehst YAML Dateien.
+
+## Was ist Continuous Integration (CI)?
+
+Continuous Integration (CI) ist eine Praxis in der Softwareentwicklung, bei der Entwickler ihre Codeänderungen _kontinuierlich_ (continuous) in ein gemeinsames Repository _integrieren_.
+Jede Integration wird automatisch durch einen Build-Prozess
+und automatisierte Tests überprüft, um sicherzustellen, dass der neue Code keine Fehler oder Konflikte einführt.
+Es gibt verschiedene CI Systeme, z.B.
+
+- GitHub Actions
+- GitLab CI/CD
+- Tekton
+
+## Motivation für CI
+
+Warum ist CI wichtig?
+Stell dir vor, du arbeitest in einem Team von Entwicklern an einem grossen Projekt.
+Deine Chefin sagt dir, dass du eine neue Funktion implementieren sollst.
+Also checkst den `main` Branch aus, implementierst die Funktion, willst sie testen
+und stellst fest, dass irgendwas nicht mehr funktioniert.
+Nach einigem Suchen findest du heraus, dass Problem schon vor deiner Änderung existierte.
+Ein Kollege hat eine Änderung gemacht, die das Problem verursacht hat, und diese
+einfach auf den `main` Branch gepusht, ohne sie zu testen.
+Das ist frustrierend, oder?
+Mit einer funktionierenden CI-Pipeline, die automatisch Builds und Tests durchführt,
+hätte dieses Problem vermieden werden können.
+Eine gute CI-Pipeline stellt sicher, dass z.B. der `main` Branch immer in einem funktionsfähigen Zustand ist.
+
+## Übung
+
+In dieser Übung lernst du, wie du eine einfache CI-Pipeline mit GitHub Actions[^1] einrichtest.
+Die Pipeline wird automatisch ausgeführt, wenn du Code in den `main` Branch pusht.
+
+### Aufgabe 1: Erstelle ein GitHub Repository
+
+1. Gehe zu [GitHub](https://github.com/new) und erstelle ein neues Repository.
+2. Initialisiere das Repository mit einer `README.md` Datei.
+3. Klicke auf `Create`.
+4. Clone das Repository auf deinen Rechner.
+5. Öffne das Repository in IntelliJ.
+6. Kopiere den Inhalt der Beispielanwendung (aus dem ZIP-File) in das Repository.
+ Anschliessend solltest du
+
+ - `./src/main/java/ch/itninja/Main.java`
+ - `./src/test/java/ch/itninja/MainTest.java`
+ - `./pom.xml`
+ - `./.gitignore`
+
+ in deinem Repository haben.
+
+7. Teste, ob Maven funktioniert.
+
+ ```shell
+ mvn test
+ ```
+
+ Das Ergebnis sollte ungefähr so aussehen
+
+ ```log
+ [INFO] Results:
+ [INFO]
+ [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
+ [INFO]
+ [INFO] ------------------------------------------------------------------------
+ [INFO] BUILD SUCCESS
+ ```
+
+8. Verwende `git add .` und `git commit` um einen Commit zu erstellen.
+9. Pushe deinen neuen Commit.
+ Öffne dein GitHub Repository und überprüfe, ob dein Push funktioniert hat.
+
+### Aufgabe 2: Erstelle eine GitHub Action, um die Tests auszuführen
+
+GitHub Actions sind das CI System von GitHub.
+In GitHub Actions definieren wir `workflows`.
+Die Workflows speichern wir im Ordner `.github/workflows`.
+Ein Workflow besteht aus einem oder mehreren Jobs.
+Ein Job kann einen oder mehrere Schritte (`steps`) haben.
+
+Das folgende Diagramm zeigt den Aufbau eines Workflows, der aus zwei Jobs besteht, die jeweils aus einem bzw. zwei Schritten bestehen.
+Der Workflow wird durch ein Ereignis (z.B. ein Push) ausgelöst.
+Die Jobs werden auf verschiedenen Runnern ausgeführt, die die Schritte abarbeiten.
+
+```mermaid
+flowchart LR
+ TRIGGER((Event))
+ TRIGGER --> Workflow
+ job1 --> runner1
+ job2 --> runner2
+ subgraph Workflow[Workflow]
+ subgraph job2["Job 2"]
+ subgraph step2_1["Step 1"]
+ action2_1_1[[actions or shell cmd]]
+ end
+ subgraph step2_2["Step 2"]
+ action2_2_1[[actions or shell cmd]]
+ end
+ end
+ subgraph job1["Job 1"]
+ subgraph step1["Step 1"]
+ action1[[actions or shell cmd]]
+ end
+ end
+
+ end
+ subgraph runner1["Runner 1"]
+ runner_action_1[[actions or shell cmd]]
+ runner_result_1[[Logs results]]
+ end
+ subgraph runner2["Runner 2"]
+ runner_action_2[[actions or shell cmd]]
+ runner_result_2[[Logs results]]
+ end
+```
+
+In dieser Aufgabe erstellst du einen Workflow, der automatisch deine JUnit Tests ausführt, wenn du Code in den `main` Branch pusht.
+
+```mermaid
+flowchart LR
+ subgraph runner1["ubuntu-latest"]
+ end
+ subgraph Workflow[java test with maven]
+ subgraph job1["job: java-test"]
+ subgraph step1["Step 1"]
+ action1_1_1[["actions/checkout@v5"]]
+ end
+ subgraph step2["Step 2"]
+ action1_2_1[["actions/setup-java@v4"]]
+ end
+ subgraph step3["Run JUnit tests using Maven"]
+ action1_3_1[["mvn test"]]
+ end
+ step1 --> step2
+ step2 --> step3
+ end
+
+ end
+ TRIGGER((Push to main))
+
+ TRIGGER -- triggers --> Workflow
+ job1 -- runs on --> runner1
+```
+
+Die Schritte sind:
+
+1. Erstelle im Root-Verzeichnis deines Projekts den Ordner `.github/workflows`.
+2. Erstelle im Ordner `.github/workflows` die Datei `java-test.yml`.
+3. Füge folgenden Inhalt in die Datei `java-test.yml` ein:
+
+ ```yaml
+ name: java test with maven
+
+ on:
+ push:
+ branches:
+ - main
+
+ jobs:
+ java-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-java@v4
+ with:
+ java-version: "21"
+ distribution: "temurin"
+ - name: Run JUnit tests using Maven
+ run: mvn test
+ ```
+
+
+ Was bedeuten die einzelnen Schlüsselworte?
+
+ - `name`: Der Name des Workflows.
+ - `on`: Definiert, wann der Workflow ausgelöst wird. In diesem Fall bei einem Push auf den `main` Branch.
+ - `jobs`: Definiert die Jobs, die ausgeführt werden sollen.
+ - `java-test`: Der Name des Jobs.
+ - `runs-on`: Definiert die Umgebung, in der der Job ausgeführt wird.
+ - `steps`: Definiert die einzelnen Schritte des Jobs.
+ - `uses`: Verwendet eine vordefinierte Action mit einer bestimmten Version.
+ - [actions/checkout](https://www.github.com/actions/checkout/tree/v5/)
+ - [actions/setup-java](https://www.github.com/actions/setup-java/tree/v4/)
+ - `name`: Der Name eines Schritts.
+ - `run`: Führt einen Shell-Befehl aus.
+
+
+
+ **Wie funktioniert dieser Workflow genau?**
+
+ Unser Workflow mit dem Namen `java test with maven` hat verschiedene Teile:
+
+ - Trigger (`on:`): Bestimmt, bei welchen Ereignissen der Workflow ausgelöst wird.
+ Im Beispiel ist das `push` auf `main`.
+
+ Häufig ergänzt man `pull_request`, damit auch PRs geprüft werden, bevor sie gemerged werden.
+ Man kann auch `schedule` nutzen, um den Workflow z.B. täglich auszuführen.
+ Oder man führt den Workflow nur aus, wenn ein Release erstellt wird.
+ Manche Workflows haben gar kein Ereignis als Trigger, sondern werden von anderen Workflows gestartet, d.h. `on: workflow_run`.
+ Dies ist nützlich für komplexe CI/CD-Pipelines, da wir so wiederverwendbare Workflows erstellen können.
+
+ - Jobs (`jobs:`): Ein Workflow kann mehrere Jobs haben, die parallel oder nacheinander ausgeführt werden.
+ In unserem Beispiel gibt es nur einen Job namens `java-test`.
+
+ Jeder Job läuft in einer frischen Umgebung (Runner) und hat seine eigenen Schritte.
+ Jobs können auch durch `needs` voneinander abhängen, d.h. ein Job startet erst, wenn ein anderer erfolgreich abgeschlossen ist.
+ Da wir aber nur einen Job haben, ist das hier nicht relevant.
+
+ Unser `java-test` Job hat folgende Bestandteile:
+
+ - `runs-on`: Legt die Ausführungsumgebung (Runner) fest — in der Regel ein Linux-, Windows- oder macOS-Runner.
+ In unserem Beispiel nutzen wir `ubuntu-latest`, d.h. die neueste verfügbare Ubuntu Version.
+ - `steps`: Die Schritte, die der Job ausführt.
+ Jeder Schritt kann eine fertige GitHub Action (`uses:`) oder einen Shell-Befehl (`run:`) ausführen.
+ GitHub Actions sind wiederverwendbare Komponenten, die bestimmte Aufgaben erledigen.
+ GitHub Actions haben den Vorteil, dass wir uns **nicht** um die Details kümmern müssen.
+ In unserem Beispiel müssen wir z.B. nicht selbst Java installieren, sondern nutzen die `actions/setup-java` Action.
+ Wir können im jeweiligen Online Repository auch die Dokumentation der Actions lesen, um zu verstehen, was sie tun und welche Eingaben sie erwarten.
+
+ Unser Job hat drei Schritte:
+
+ - `uses: actions/checkout@v5`: Der erste Schritt nutzt `git` bzw. die `actions/checkout` GitHub Action, um den Code aus dem Repository in die Runner-Umgebung zu klonen.
+
+ Bei GitHub Actions können wir git Commit Hashes, Branch-Namen, Tags oder Versionsnummern angeben, um eine spezifische Version der Action zu nutzen.
+ Im Beispiel verwenden wir hier die Version `v5`, d.h. wir verwenden den aktuellsten Tag, der mit `v5` beginnt (aktuell ist das `v5.0.0`, könnte aber in Zukunft `v5.3.2` oder höher sein).
+ Wenn wir immer die neueste Version einer Action nutzen wollen, könnten wir auch `@main` angeben, was aber nicht empfohlen wird, da es zu unerwarteten Änderungen führen kann.
+ Wenn wir eine spezifischere Version wie `v5.3.2` angeben wollen, könnten wir das auch tun.
+ Wenn wir immer diesselbe Version einer Action nutzen wollen, auch wenn der Maintainer Tags löscht und neu erstellt, könnten wir auch den Commit Hash angeben, z.B. `@a1b2c3d4e`.
+
+ - `uses: actions/setup-java@v4`: Installiert die gewünschte JDK-Version, damit `mvn` mit der richtigen Java-Version läuft.
+
+ Hier nutzen wir, dass GitHub Actions `inputs` unterstützen, die wir mit `with:` angeben können.
+ In unserem Beispiel geben wir die Java-Version `21` und die Distribution `temurin` an.
+ Die Action installiert dann automatisch das passende JDK.
+
+ - `run: mvn test`: Führt Maven aus und startet Build und Tests.
+ Hier führen wir einen Shell-Befehl aus, der Maven startet und die Tests ausführt.
+ Für die Darstellung in der GitHub Actions Oberfläche ist es hilfreich, wenn wir dem Schritt einen Namen geben, z.B. `Run JUnit tests using Maven`.
+
+4. Committe und pushe die Änderungen.
+5. Gehe zu deinem GitHub Repository und klicke auf den Reiter `Actions`.
+ Du solltest sehen, dass dein Workflow ausgeführt wird.
+6. Klicke auf den laufenden Workflow, um die Details zu sehen.
+7. Klicke auf den Job `java-test` und dann auf den Step `Run JUnit tests using Maven`.
+ Du solltest die Ausgabe von Maven sehen.
+8. Am Ende sollte der Workflow erfolgreich sein und mit einem ✅ markiert sein.
+
+### Aufgabe 3: Teste Deine CI
+
+1. Füge dem Java Code einen Fehler hinzu, der verhindert, dass das Java Programm
+ kompiliert werden kann oder die Tests fehlschlagen lässt.
+2. Committe und pushe die Änderungen.
+3. Gehe zu deinem GitHub Repository und klicke auf den Reiter `Actions`.
+ Warte, bis dein Workflow abgeschlossen ist.
+ Der Workflow sollte fehlschlagen und mit einem ❌ markiert sein.
+4. Klicke auf den Workflow, um die Details zu sehen.
+5. Klicke auf den Job `java-test` und dann auf den Step `Run JUnit tests using Maven`.
+ Du solltest die Ausgabe von Maven sehen und dort auf den Fehler
+ hingewiesen werden, den du gerade eingeführt hast.
+6. Fixe den Fehler wieder.
+7. Committe und pushe die Änderungen.
+8. Der Workflow sollte wieder erfolgreich sein und mit einem ✅ markiert sein.
+
+[^1]: Diese Aufgabe basiert auf
diff --git a/content/2025/labs/99_shared/ci/ci-example.zip b/content/2025/labs/99_shared/ci/ci-example.zip
new file mode 100644
index 000000000..c7c5cdf12
Binary files /dev/null and b/content/2025/labs/99_shared/ci/ci-example.zip differ