GitHub Actions Grundlagen: Workflow, Job und Step
1. Übersicht
GitHub Actions ist ein in GitHub integriertes CI/CD-Tool, mit dem du Build, Test und Deployment automatisieren kannst, ohne einen separaten CI-Server zu benötigen. Dieser Artikel erklärt drei Kernkonzepte: workflow, job und step, mit praktischen Beispielen für ein Spring-Boot-Projekt mit Maven.
Nach dem Lesen wirst du in der Lage sein, eine Workflow-Datei zu schreiben, die automatisch Tests ausführt, wenn du Code pushst. Du wirst verstehen, wie Jobs parallel oder sequenziell ausgeführt werden, und wie du fehlgeschlagene Workflows debuggst.
2. Was ist ein Workflow?
Ein Workflow ist eine YAML-Datei, die sich im Verzeichnis .github/workflows/ deines Repositorys befindet. Jeder Workflow definiert, wann er ausgelöst wird (Trigger) und was er tut (Jobs).
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Maven
run: mvn -B package
Erklärung:
nameam Anfang der Datei ist der Name, der auf der Registerkarte Actions von GitHub angezeigt wird.ondeklariert den Trigger: hier wird der Workflow ausgelöst, wenn einpushoderpull_requestzum Branchmainerfolgt.jobs.buildist ein einzelner Job namensbuild, der auf der virtuellen Maschineubuntu-latestläuft.
Hinweis: Die Datei muss im Pfad
.github/workflows/*.ymlliegen. Wenn das Verzeichnis falsch ist, erkennt GitHub den Workflow nicht.
3. Job: unabhängige Ausführungseinheit
Ein Workflow kann mehrere Jobs haben. Jeder Job läuft auf einem separaten Runner (virtuelle Maschine). Standardmäßig werden Jobs parallel ausgeführt, es sei denn, du deklarierst needs.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: mvn -B test
deploy:
runs-on: ubuntu-latest
needs: test
steps:
- run: echo "Deploying application..."
Erklärung:
testunddeploysind zwei separate Jobs; jeder Job hat eine saubere Runner-Umgebung und teilt keine Dateien mit anderen.needs: testzwingtdeploydazu, auf die erfolgreiche Ausführung vontestzu warten — dadurch entsteht eine sequenzielle Abhängigkeit.- Ohne
needswürde GitHub Actionstestunddeploygleichzeitig ausführen.
3.1. Datenaustausch zwischen Jobs
Da jeder Job seinen eigenen Runner hat, musst du artifacts verwenden, um Dateien zwischen Jobs zu übertragen.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: mvn -B package
- uses: actions/upload-artifact@v4
with:
name: app-jar
path: target/*.jar
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/download-artifact@v4
with:
name: app-jar
Erklärung:
upload-artifactspeichert die.jar-Datei vom Jobbuildim temporären GitHub-Speicher.download-artifactim Jobdeployruft die Datei auf den neuen Runner ab.- Dies ist der einzige Weg, um Daten zwischen Jobs zu übertragen (normale Umgebungsvariablen funktionieren nicht).
4. Step: die kleinste Handlungseinheit
Ein Step ist eine spezifische Aktion innerhalb eines Jobs, die sequenziell von oben nach unten ausgeführt wird. Es gibt zwei Haupttypen von Steps: uses (um vordefinierte Actions aufzurufen) oder run (um Shell-Befehle auszuführen).
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- name: Run tests
run: mvn -B test
- name: Print result
run: echo "Test step finished with status ${{ job.status }}"
Erklärung:
- Der Step
Checkout codeverwendet die vordefinierte Actionactions/checkout, um den Quellcode auf den Runner zu bringen. - Der Step
Cache Maven dependenciescached das Verzeichnis~/.m2/repository, wodurch nachfolgende Builds schneller werden, da die Abhängigkeiten nicht erneut heruntergeladen werden müssen. - Der
keydes Caches basiert auf dem Hash der Dateipom.xml— wenn sichpom.xmländert, wird der alte Cache verworfen und ein neuer erstellt. - Der letzte Step verwendet
run, um normale Shell-Befehle auszuführen. Es können Kontextreferenzen wie${{ job.status }}verwendet werden.
5. Umgebungsvariablen und Secrets
Oft musst du vertrauliche Konfigurationen (API-Schlüssel, Tokens) an den Workflow übergeben, ohne sie hartcodiert in der YAML-Datei zu speichern.
jobs:
deploy:
runs-on: ubuntu-latest
env:
SPRING_PROFILES_ACTIVE: prod
steps:
- uses: actions/checkout@v4
- name: Deploy to server
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
echo "Deploying with profile $SPRING_PROFILES_ACTIVE"
curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy
Erklärung:
envauf Job-Ebene gilt für alle Steps in diesem Job (SPRING_PROFILES_ACTIVE).envauf Step-Ebene gilt nur für diesen Step (DEPLOY_TOKEN).secrets.DEPLOY_TOKENwird aus Settings → Secrets and variables → Actions des Repositorys abgerufen und zeigt niemals den tatsächlichen Wert im Log.
Hinweis: Gib einen Secret-Wert nie direkt mit
echo $DEPLOY_TOKENins Log aus — GitHub wird ihn zwar automatisch maskieren, aber es ist besser, Secrets gar nicht erst auszugeben.
6. Matrix: mehrere Versionen gleichzeitig ausführen
Wenn du auf mehreren Java-Versionen oder Betriebssystemen testen musst, hilft dir matrix, wiederholte Job-Definitionen zu vermeiden.
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
java: [17, 21]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
- run: mvn -B test
Erklärung:
strategy.matrix.javadeklariert eine Liste von Werten[17, 21].- GitHub Actions erstellt automatisch 2 Jobs parallel, jeder mit einer entsprechenden Java-Version.
- Dies spart wiederholtes YAML-Schreiben und ist ideal, um sicherzustellen, dass der Code in mehreren Umgebungen funktioniert.
7. Workflow-Tests mit einfachen Tests
Workflows werden nicht direkt mit JUnit getestet, aber du solltest sicherstellen, dass der Schritt mvn test im Workflow die tatsächliche Test-Suite des Projekts ausführt. Hier ist ein einfaches Test-Beispiel für OrderService, das von CI ausgeführt wird:
class OrderServiceTest {
private final OrderService orderService = new OrderService();
@Test
void shouldCalculateTotalPriceCorrectly() {
Order order = new Order("ORD-001", List.of(
new OrderItem("Keyboard", 2, BigDecimal.valueOf(50)),
new OrderItem("Mouse", 1, BigDecimal.valueOf(20))
));
BigDecimal total = orderService.calculateTotal(order);
assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(120));
}
}
Erklärung:
- Dieser Test befindet sich in
src/test/javaund wird vonmvn -B testim Workflow-Step automatisch ausgeführt. - Wenn ein Test fehlschlägt, signalisiert der Step
Run testseinen Fehler (Exit-Code ≠ 0), der Job stoppt und abhängige Jobs (needs) werden nicht ausgeführt. - Dies ist, warum CI wertvoll ist: fehlerhafter Code wird blockiert, bevor er in
maingemergt wird.
Ergebnis bei Ausführung in der GitHub Actions UI (Registerkarte Actions):
✓ Checkout code
✓ Set up JDK 21
✓ Build with Maven
✓ Run tests (shouldCalculateTotalPriceCorrectly PASSED)
8. Häufige Fehler
- Falsches YAML-Indent: GitHub Actions verwendet YAML. Ein falsches Einrückungsmuster um ein Leerzeichen kann dazu führen, dass Jobs oder Steps nicht erkannt werden – typischerweise mit der Fehlermeldung “Invalid workflow file”.
- Vergessenes
runs-on: Jeder Job mussruns-onhaben. Ohne dieses Feld schlägt der Workflow beim Parsen fehl. - Verwendung von Umgebungsvariablen eines Jobs in einem anderen Job:
envund Ausgabevariablen werden nicht automatisch zwischen Jobs übertragen – du musstoutputsoderartifactverwenden. - Cache-Schlüssel ändert sich nicht, wenn sich Abhängigkeiten ändern: Wenn der
keyzu allgemein ist (z.B. nurmaven-cache), verwendest du versehentlich einen alten Cache mit veralteten Abhängigkeiten. - Ausführung von schweren Workflows bei jedem Push: Wenn du
pathsoderbranchesnicht einschränkst, triggert jeder kleine Commit (z.B. README-Änderung) die gesamte Build- und Test-Pipeline – das verschlingt kostenlose Actions-Minuten (es gibt ein Limit).
Hinweis: Wenn dein Projekt nur einen einfachen Build benötigt und nicht auf mehreren Umgebungen laufen muss, brauchst du weder
matrixnochartifact— unnötige Komplexität macht den Workflow schwer zu warten.
9. Fazit
Ein Workflow ist eine YAML-Datei, die die gesamte Pipeline definiert. Jobs sind unabhängige Ausführungsblöcke, die auf separaten Runnern laufen, und Steps sind konkrete Aktionen innerhalb eines Jobs. Wenn du diese drei Ebenen verstehst, kannst du eine klare CI/CD-Pipeline organisieren: welche Jobs parallel laufen, welche auf andere warten müssen (needs), und wann du artifact zum Datenaustausch verwendest.
Wenn du neu anfängst, solltest du den Workflow einfach halten (ein Job mit wenigen grundlegenden Steps: checkout, Java-Setup, Test). Wenn dein Projekt wächst, füge matrix, cache hinzu und teile build/test/deploy in separate Jobs auf – das macht die Verwaltung und das Debugging einfacher.