Der Spickzettel zur zweiten Hälfte des Seminars GitHub Actions in der Praxis: Welcher Runner wofür taugt, wie ein Image benannt und veröffentlicht wird, wo ein Geheimnis hingehört, wie OIDC gespeicherte Zugangsschlüssel ersetzt und woran die Herkunft eines Artefakts nachweisbar wird. Alles, was vorher in der Workflow-Datei steht, findet sich auf dem ersten Blatt: GitHub Actions: Workflows und Pipelines.
Maßgeblich für Schlüsselnamen und Versionsdetails ist die GitHub-Actions-Dokumentation; dieses Blatt trifft eine Auswahl — die Entscheidungsraster, die einen Versionssprung überleben.
Runner: gehostet oder selbst betrieben
| GitHub-hosted | self-hosted | |
|---|---|---|
| Bereitstellung | von GitHub, frisch je Job | selbst betrieben |
| Umgebung | vorinstalliert, immer sauber | frei, aber selbst gepflegt |
| Abrechnung | Minuten, öffentlich frei | eigene Infrastruktur |
| Zugang | nur über das Internet | auch ins interne Netz |
Für öffentliche Repositories rät GitHub von self-hosted Runnern ab — fremder Code auf eigener Hardware.
| Plattform | öffentliche Repos | private Repos |
|---|---|---|
| Linux | 4 CPUs, 16 GB RAM | 2 CPUs, 8 GB RAM |
| Windows | 4 CPUs, 16 GB RAM | 2 CPUs, 8 GB RAM |
| macOS ARM | 3 CPUs, 7 GB RAM | gleiche Ausstattung |
Alle Standard-Runner bringen 14 GB SSD mit. Larger Runners lohnen für mehr CPU, Speicher und Plattenplatz, für statische IP-Adressen und für ARM- oder GPU-Zielarchitekturen — sie kosten mehr je Minute, der Gewinn muss also an der Laufzeit messbar sein. Ein fehlender Cache wird davon nicht besser.
Module: Runner, Administration und Skalierung · Continuous Integration und Delivery verstehen
Persistent oder ephemer
| persistenter Runner | ephemerer Runner | |
|---|---|---|
| Lebensdauer | dauerhaft | ein Job |
| Rückstände | bleiben liegen | verschwinden |
| Startzeit | sofort verfügbar | Aufbau je Job |
| Eignung | vertrauenswürdige Repos | sensible Workloads |
Just-in-Time-Runner registrieren sich für genau einen Job und werden danach verworfen. Runner Groups begrenzen, welche Repositories eine Maschine überhaupt benutzen dürfen; ohne sie steht sie der ganzen Organisation offen.
Modul: GitHub Actions sicher betreiben
Image-Tags und Releases
| Tag | Entsteht aus | Taugt für |
|---|---|---|
| latest | letzter Stand von main | lokales Ausprobieren |
| 2.4.1 | Git-Tag der Version | Produktion, Rollback |
| main-a1b2c3d | Branch und Commit | Fehlersuche, Staging |
| pr-482 | Pull-Request-Nummer | Vorschauumgebung |
Für ein Deployment zählt am Ende der Digest — Tags lassen sich verschieben, ein Digest nicht.
permissions:
contents: read
packages: write
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- id: push
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Ohne packages: write scheitert erst der Push — die Anmeldung selbst gelingt
noch. Der Push-Schritt liefert unter outputs.digest den Digest, an dem später
die Attestation hängt. Ein Release entsteht aus einem Tag, nicht aus einem
Branch, und enthält Versionsnummer, Release Notes aus Sicht der Nutzenden, die
installierbaren Artefakte und den Verweis auf das zugehörige Image.
Modul: Container-Images, Packages und Releases
Environments und Schutzregeln
| Regel | Wirkung | Grenze |
|---|---|---|
| Required Reviewers | wartet auf Freigabe | bis zu 6 Personen oder Teams |
| Wait Timer | verzögert den Start | in Minuten festgelegt |
| Deployment Branches | nur genannte Branches und Tags | Muster je Umgebung |
| Custom Rules | Prüfung durch eine GitHub App | App muss installiert sein |
Eine Freigabe genügt, auch wenn mehrere Prüfende eingetragen sind; Selbstfreigabe lässt sich unterbinden.
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: produktion
url: https://beetkarte.example
steps:
- run: ./rollout.sh
env:
TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Denselben Secret-Namen kann jedes Environment mit einem eigenen Wert belegen —
der Workflow bleibt gleich. Der Weg zur Produktion führt vom Merge auf main
über ein automatisches Staging und die Freigabe zur Produktion; die Freigabe
wird an der Umgebung geregelt, nicht im Workflow verhandelt.
Modul: Deployments und Environments
Wo ein Wert hingehört
| Ort | Sichtbar für | Typischer Inhalt |
|---|---|---|
| Organization Secret | ausgewählte Repositories | gemeinsamer Registry-Zugang |
| Repository Secret | alle Workflows des Repos | Dienstschlüssel des Projekts |
| Environment Secret | Jobs nach erfüllten Regeln | Produktionszugang |
| Variable | alle, im Klartext | Registry-Name, Node-Version |
Strukturierte Werte wie JSON gehören nie in ein Secret — die Maskierung greift
dort nicht zuverlässig, und sie ist ohnehin eine Notbremse, kein Schutzkonzept.
Ein Personal Access Token hängt an einer Person und verschwindet mit ihr; eine
GitHub App hat eigene, fein einstellbare Rechte, überlebt Personalwechsel und
stellt kurzlebige Token je Lauf aus. Für alles innerhalb des Repositories genügt
ohnehin das GITHUB_TOKEN.
permissions: {}
jobs:
veroeffentlichen:
permissions:
contents: read
packages: write
id-token: write
Grundsatz oben, Ausnahme am Job: Rechte am einzelnen Job halten den Schaden
lokal. Ein Token mit Schreibrecht auf contents kann den Workflow selbst
verändern. Seit September 2026 gibt es zusätzlich vulnerability-alerts für den
Zugriff auf Dependabot-Meldungen.
Modul: Secrets, Tokens und OIDC
OIDC statt gespeicherter Schlüssel
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.DEPLOY_ROLE_ARN }}
aws-region: eu-central-1
Ohne id-token: write kann der Job das OIDC-Token gar nicht erst anfordern —
ein Fehler, der leicht für ein Problem der Cloud-Rolle gehalten wird.
Die Vertrauensbeziehung eng schneiden: Der Claim sub bindet den Zugang an
Repository und Branch oder Environment, etwa
repo:gartenwerk/beetkarte:ref:refs/heads/main. Für Produktion an das
Environment binden, nicht an den Branch allein — ein zu weit gefasster Claim
erlaubt jedem Fork-Branch den Zugriff auf die Cloud. Und nach dem Umstieg den
alten Zugangsschlüssel widerrufen, nicht nur unbenutzt liegen lassen.
Modul: Secrets, Tokens und OIDC
Herkunft nachweisen: Attestations und SBOM
permissions:
id-token: write
attestations: write
packages: write
- uses: actions/attest@v4
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
Der Nachweis hängt am Digest, nicht am Tag — deshalb bleibt er gültig, wenn ein Tag verschoben wird. Er belegt, welcher Workflow aus welchem Commit dieses Artefakt gebaut hat; er verhindert nicht, dass ein Build fehlerhaften Code enthält. Sein Wert bemisst sich an der Absicherung des Workflows, der ihn ausstellt.
gh attestation verify ./dist/beetkarte.tgz \
-R gartenwerk/beetkarte
gh attestation verify \
oci://ghcr.io/gartenwerk/beetkarte:2.4.1 \
-R gartenwerk/beetkarte
Schlägt die Prüfung fehl, endet der Befehl mit einem Fehler — im Deployment-Job
hält das den Lauf an, sofern kein continue-on-error daneben steht. Die SBOM
entsteht beim Build, wo die tatsächlichen Stände bekannt sind; sie beantwortet
bei einer neuen Schwachstelle ohne Suche, welche Stände betroffen sind. Die
Signatur- und Transparenzinfrastruktur dahinter stellt Sigstore.
Modul: Software Supply Chain und Artifact Attestations
Governance und Kennzahlen
actions/checkout@v6
gartenwerk/*
!gartenwerk/experiment@*
gartenwerk/cicd/.github/workflows/ci.yml@v2
So sieht eine Auswahlliste erlaubter Actions aus; lokale Actions und Reusable
Workflows des eigenen Repositories sind immer erlaubt. Governance muss vier
Fragen beantworten: welche fremden Actions in die Pipelines dürfen, welche
Standardrechte das GITHUB_TOKEN in neuen Repositories bekommt, wer Workflows
von Fork-Beiträgen freigibt und welche Vorlage neue Projekte als Golden Path
erhalten.
| Kennzahl | Warnzeichen | Erste Maßnahme |
|---|---|---|
| Laufzeit bis Rückmeldung | über 15 Minuten | Prüfungen aufteilen |
| Wartezeit auf Runner | steigt zu Stoßzeiten | Kapazität, Läufe filtern |
| Fehlerquote ohne Codefehler | über 5 Prozent | instabile Tests isolieren |
| Minuten je Woche | wächst ohne neue Projekte | doppelte Läufe abstellen |
Ohne Ausgangsmessung lässt sich später nicht belegen, ob eine Änderung geholfen hat.
Modul: Governance, Monitoring und KI-Unterstützung
Typische Fallen
- Ereigniswert direkt interpoliert.
${{ github.event.issue.title }}in einemrunwird zum Kommando; der Wert gehört überenvals Daten in den Schritt. - Fork-Code mit Secrets ausgeführt.
pull_request_targetplus Checkout des Beitrags gibt das Repository preis. - Action folgt einem Branch. Ohne SHA-Pin landet ein fremder Commit ungeprüft in der Pipeline — und ein gepinnter SHA, den nie jemand aktualisiert, hält Sicherheitskorrekturen draußen.
write-allals Reparatur. Gesetzt, weil ein Schritt scheiterte; danach erbt jeder Job alles. Entfernte Schritte lassen ihre Rechte zurück.- Produktionszugang als Repository-Secret. Damit steht er jedem Workflow offen, statt an der Umgebung zu hängen.
- Staging auf der Produktionsdatenbank. Zwei Umgebungen, ein Datenbestand — die Trennung existiert dann nur auf dem Papier.
- Environment ohne Schutzregel. Vorhanden, aber ohne Reviewer, Branch-Muster oder Timer — die Freigabe ist damit reine Dekoration.
- Rollback nie erprobt. Beschrieben ist er, ausgeführt nicht; ein Tag, der inzwischen auf einen anderen Stand zeigt, macht ihn wertlos.
- Deployment baut neu. Statt das geprüfte Image zu ziehen, entsteht ein zweites Artefakt — geprüft wurde dann etwas anderes als ausgeliefert wird.
- Nur
latestam Image. Später weiß niemand, welcher Commit darin steckt. - Attestation vor dem Push. Sie bezieht sich dann auf einen anderen Digest; ebenso wertlos ist ein Nachweis, den nie jemand prüft.
- Runner im Produktionsnetz. Er erreicht Systeme, die kein Build braucht — und ein Job hinterlässt Zugangsdaten, die der nächste findet.
- KI-Vorschlag ungeprüft übernommen. Erfundene Actions sehen plausibel aus,
und
write-allfunktioniert immer; grün heißt nicht geprüft.