GitHub Actions hat sich seit 2022 dramatisch weiterentwickelt. Wer heute noch einfache push → test → deploy-Pipelines schreibt, verschenkt Potenzial. In diesem Artikel zeigen wir, wie Claude Code hilft, komplexe, produktionsreife Workflows zu generieren — von Matrix-Builds über Reusable Workflows bis hin zu OIDC-basierter Cloud-Authentifizierung ohne ein einziges Secret.
💡 Claude Code als Pipeline-Architekt: Die Beispiele in diesem Artikel wurden mit Claude Code generiert und dann auf echten Projekten verifiziert. Der Prompt lautet typischerweise: "Erstelle einen GitHub Actions Workflow für [Anforderung] mit best practices für 2026."
1. Workflow-Grundlagen: Trigger, Jobs und Abhängigkeiten
Ein solider GitHub Actions Workflow beginnt mit einer klaren Trigger-Definition. Viele Teams nutzen nur push und pull_request — dabei bietet GitHub Actions weit mehr: workflow_dispatch für manuelle Ausführungen, schedule für nächtliche Builds, repository_dispatch für externe Events und workflow_call für komposierbare Pipelines.
YAML
Vollständiger Basis-Workflow mit Outputs, Conditionals und Job-Abhängigkeiten
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
paths-ignore:
- '**.md'
- '.github/CODEOWNERS'
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
environment:
description: 'Deploy-Zielumgebung'
required: true
default: 'staging'
type: choice
options: [staging, production]
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint & Type Check
runs-on: ubuntu-24.04
outputs:
has-changes: ${{ steps.changes.outputs.src }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- id: changes
uses: dorny/paths-filter@v3
with:
filters: |
src:
- 'src/**'
- 'package*.json'
- run: npm ci
- run: npm run lint
- run: npm run type-check
test:
name: Unit Tests
needs: lint
if: ${{ needs.lint.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage --reporter=json
- name: Upload Coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7
deploy:
name: Deploy
needs: [lint, test]
if: ${{ github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }}
environment: production
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: echo "Deploying to ${{ inputs.environment || 'production' }}"
Beachte das concurrency-Feld: Es sorgt dafür, dass bei einem neuen Push auf denselben Branch der laufende Workflow automatisch abgebrochen wird. Das verhindert veraltete Deployments und spart Runner-Minuten.
Claude Code Tipp: Frage Claude Code nach der Workflow-Strategie: "Welche concurrency-Strategie macht für Feature-Branches vs. main-Branch Sinn?" — Claude erklärt den Unterschied zwischen cancel-in-progress: true (Feature) und cancel-in-progress: false (Production) präzise.
Job-Outputs und bedingte Abhängigkeiten
Der needs-Schlüssel definiert Abhängigkeiten zwischen Jobs. Über outputs können Jobs Daten an nachfolgende Jobs übergeben — zum Beispiel ob sich relevante Dateien geändert haben. Der if-Ausdruck macht Jobs bedingt ausführbar und spart Runner-Zeit.
2. Matrix Strategy: Multi-OS und Multi-Version-Tests
Matrix-Builds sind eines der mächtigsten Features von GitHub Actions. Statt mehrere fast identische Jobs zu schreiben, definiert man eine Matrix aus Variablen — GitHub Actions erstellt daraus automatisch alle Kombinationen.
MATRIX
Matrix mit Node-Versionen, Betriebssystemen, include und exclude
jobs:
test-matrix:
name: Test (Node ${{ matrix.node }} / ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 4
matrix:
os: [ubuntu-24.04, windows-2022, macos-14]
node: ['18', '20', '22']
experimental: [false]
include:
# Node 23 (current) testen, aber Fehler erlaubt
- os: ubuntu-24.04
node: '23'
experimental: true
# Windows + Node 22 braucht extra Coverage-Flag
- os: windows-2022
node: '22'
coverage: true
exclude:
# Node 18 auf macOS nicht mehr unterstuetzt
- os: macos-14
node: '18'
continue-on-error: ${{ matrix.experimental }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Run Tests
run: npm test
- name: Run Tests with Coverage (only flagged combos)
if: ${{ matrix.coverage == true }}
run: npm test -- --coverage
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}-node${{ matrix.node }}
path: test-results/
🔄
fail-fast: false
Alle Matrix-Kombinationen laufen durch, auch wenn eine fehlschlägt. Besseres Debugging.
⚡
max-parallel: 4
Begrenzt parallele Läufe. Wichtig bei Self-Hosted Runnern mit beschränkter Kapazität.
➕
include
Fügt spezifische Kombinationen zur Matrix hinzu oder erweitert bestehende um Extra-Felder.
➖
exclude
Entfernt unerwünschte Kombinationen wie unsupported OS/Version Paare.
Achtung bei continue-on-error: Ein Job mit continue-on-error: true gilt für den Workflow als "erfolgreich", auch wenn er scheitert. Das experimental-Pattern ist ideal für Zukunfts-Tests die keinen Build blockieren sollen.
3. Reusable Workflows: DRY-Prinzip für Pipelines
Wer mehrere Repositories betreibt, kennt das Problem: Dieselben CI-Schritte werden in jedem Repo dupliziert. Eine Änderung am Deploy-Script bedeutet manuelle Updates in 10 Repositories. Reusable Workflows lösen dieses Problem elegant durch workflow_call.
REUSABLE
Wiederverwendbarer Workflow: .github/workflows/_deploy.yml (im shared Repo)
# .github/workflows/_deploy.yml
# Konvention: Unterstrich-Prefix = reusable, nicht direkt triggerbar
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
description: 'staging | production'
image-tag:
required: true
type: string
region:
required: false
type: string
default: 'eu-central-1'
dry-run:
required: false
type: boolean
default: false
secrets:
inherit # Alle Secrets des Caller-Repos werden vererbt
outputs:
deployment-url:
description: 'URL der deployten Umgebung'
value: ${{ jobs.deploy.outputs.url }}
jobs:
deploy:
name: Deploy to ${{ inputs.environment }}
runs-on: ubuntu-24.04
environment: ${{ inputs.environment }}
outputs:
url: ${{ steps.deploy-step.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Deploy (dry-run check)
id: deploy-step
env:
IMAGE_TAG: ${{ inputs.image-tag }}
REGION: ${{ inputs.region }}
DRY_RUN: ${{ inputs.dry-run }}
run: |
if [[ "$DRY_RUN" == "true" ]]; then
echo "Dry-run: Deploy von $IMAGE_TAG nach ${{ inputs.environment }} in $REGION uebersprungen"
else
./scripts/deploy.sh "$IMAGE_TAG" "${{ inputs.environment }}" "$REGION"
echo "url=https://${{ inputs.environment }}.myapp.com" >> $GITHUB_OUTPUT
fi
CALLER
Caller-Workflow im Produkt-Repo: referenziert den shared Workflow
# .github/workflows/deploy-production.yml (im Produkt-Repo)
name: Production Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-24.04
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,prefix=sha-
type=semver,pattern={{version}}
- run: docker build -t ${{ steps.meta.outputs.tags }} .
staging:
needs: build
uses: my-org/shared-workflows/.github/workflows/_deploy.yml@main
with:
environment: staging
image-tag: ${{ needs.build.outputs.image-tag }}
dry-run: false
secrets: inherit
production:
needs: staging
uses: my-org/shared-workflows/.github/workflows/_deploy.yml@main
with:
environment: production
image-tag: ${{ needs.build.outputs.image-tag }}
secrets: inherit
Mit secrets: inherit werden alle Secrets des Caller-Repos automatisch an den Reusable Workflow weitergegeben — ohne explizite Auflistung. Das reduziert Boilerplate erheblich.
4. Caching: npm, pnpm und Cargo effizient beschleunigen
Caching ist der schnellste Weg, CI-Laufzeiten zu halbieren. GitHub Actions bietet actions/cache mit intelligenten Cache-Keys und Restore-Keys. Claude Code kennt die optimalen Patterns für alle gängigen Package Manager.
CACHE
npm, pnpm und Cargo Cache-Patterns im Vergleich
steps:
- uses: actions/checkout@v4
# npm (klassisch)
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
npm-
# pnpm (empfohlen fuer Monorepos)
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: '9'
- name: Get pnpm store path
id: pnpm-cache
run: echo "STORE=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-${{ runner.os }}-
# Cargo/Rust (inkl. Compiler-Cache)
- name: Cache Rust/Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs') }}
restore-keys: |
cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-
cargo-${{ runner.os }}-
# Python / pip
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('**/requirements*.txt', '**/pyproject.toml') }}
restore-keys: |
pip-${{ runner.os }}-
| Package Manager |
Cache-Pfad |
Lockfile für hashFiles() |
Typische Ersparnis |
| npm |
~/.npm |
package-lock.json |
60–80 Sek. |
| pnpm |
pnpm store (dynamisch) |
pnpm-lock.yaml |
70–90 Sek. |
| Cargo |
~/.cargo + target/ |
Cargo.lock + *.rs |
3–8 Min. |
| pip |
~/.cache/pip |
requirements*.txt |
30–60 Sek. |
Cache-Key Strategie: Der restore-keys-Fallback ermöglicht Partial-Cache-Hits. Wenn kein exakter Key gefunden wird, wird der nächstpassende Cache geladen — besser als kein Cache. Claude Code generiert automatisch korrekte Fallback-Hierarchien.
5. OIDC-Authentifizierung: Keine Secrets mehr für Cloud-Deploys
Einer der wichtigsten Sicherheitsfortschritte der letzten Jahre: GitHub Actions kann sich ohne langlebige Secrets bei AWS, Azure und GCP authentifizieren. Stattdessen nutzt es OpenID Connect (OIDC) — kurzlebige Tokens die direkt von GitHub ausgestellt werden.
🔐 Warum OIDC? Langlebige AWS Access Keys oder GCP Service Account Keys in GitHub Secrets sind ein Sicherheitsrisiko: Sie können leaken, rotieren nicht automatisch und haben oft zu viele Rechte. OIDC-Tokens leben maximal eine Stunde und sind an den spezifischen Workflow gebunden.
OIDC
AWS OIDC: Deploy zu ECR/ECS ohne Access Keys
name: Deploy via OIDC
on:
push:
branches: [main]
permissions:
id-token: write # PFLICHT fuer OIDC!
contents: read
jobs:
deploy-aws:
name: Deploy to AWS (OIDC)
runs-on: ubuntu-24.04
environment: production
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions-Deploy
role-session-name: GitHubActions-${{ github.run_id }}
aws-region: eu-central-1
# Kein aws-access-key-id, kein aws-secret-access-key!
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Build und Push Docker Image
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/myapp:$IMAGE_TAG .
docker push $ECR_REGISTRY/myapp:$IMAGE_TAG
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster production \
--service myapp \
--force-new-deployment \
--region eu-central-1
# Azure OIDC (aequivalent)
deploy-azure:
name: Deploy to Azure (OIDC)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Azure Login via OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# Kein Passwort -- OIDC Federated Credentials!
# GCP OIDC (aequivalent)
deploy-gcp:
name: Deploy to GCP (OIDC)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/123/locations/global/workloadIdentityPools/github/providers/github'
service_account: 'deploy-sa@my-project.iam.gserviceaccount.com'
AWS IAM Trust Policy für OIDC
Auf AWS-Seite muss eine IAM-Rolle mit einer Trust Policy erstellt werden, die GitHub Actions als Identity Provider vertraut:
IAM
AWS Trust Policy — präzise auf Repository und Branch einschränken
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub":
"repo:my-org/my-repo:ref:refs/heads/main"
}
}
}]
}
Die Condition schränkt die Rolle auf genau einen Branch eines bestimmten Repos ein. Das Least-Privilege-Prinzip wird so auch auf OIDC-Ebene durchgesetzt. Claude Code generiert diese Trust Policies automatisch wenn man das Repository und den Branch angibt.
6. Container Jobs & Self-Hosted Runner
Für Integrationstests braucht man oft echte Dienste — PostgreSQL, Redis, Elasticsearch. GitHub Actions bietet dafür Service Container: Datenbank-Container laufen parallel zum Test-Job und sind über localhost erreichbar.
CONTAINER
Service Container: PostgreSQL + Redis für Integrationstests
jobs:
integration-tests:
name: Integration Tests mit DB & Cache
runs-on: ubuntu-24.04
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
options: |
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: |
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 3
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Datenbankschema migrieren
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
run: npm run db:migrate
- name: Integrationstests ausfuehren
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
NODE_ENV: test
run: npm run test:integration
- name: Test-Artefakte hochladen
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-test-results
path: test-results/
retention-days: 14
Self-Hosted Runner: Wann und wie
GitHub-gehostete Runner sind für die meisten Projekte ausreichend. Self-Hosted Runner lohnen sich, wenn du spezielle Hardware brauchst (GPU, ARM-CPUs), große Artefakte nicht transferieren möchtest oder strikte Datenschutzanforderungen hast.
RUNNER
Self-Hosted Runner mit Labels und Docker-in-Docker
jobs:
build-on-custom-runner:
name: Build auf Self-Hosted (ARM64)
# Labels ermoeglichen gezieltes Routing zu spezifischen Runnern
runs-on: [self-hosted, linux, arm64, production]
timeout-minutes: 30 # Wichtig! Verhindert haengende Jobs
steps:
- uses: actions/checkout@v4
# Docker-in-Docker: Docker-Befehle innerhalb des Runner-Containers
- name: Build Multi-Arch Image
run: |
docker buildx create --use --name multiarch
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ghcr.io/${{ github.repository }}:${{ github.sha }} \
--push \
.
e2e-on-gpu-runner:
name: E2E Tests mit GPU (AI-Features)
runs-on: [self-hosted, gpu, cuda-12]
container:
image: nvidia/cuda:12.3.1-runtime-ubuntu22.04
options: --gpus all
steps:
- uses: actions/checkout@v4
- name: GPU-gestuetzte Tests
run: python -m pytest tests/ai/ --gpu
cleanup:
name: Runner Cleanup
runs-on: [self-hosted, linux]
if: always()
needs: [build-on-custom-runner, e2e-on-gpu-runner]
steps:
- name: Docker Images aufraeumen
run: docker system prune -f --filter "until=24h"
Security bei Self-Hosted Runnern: Self-Hosted Runner auf Public Repos sind gefährlich — ein Fork-PR könnte beliebigen Code ausführen. Beschränke Self-Hosted Runner auf private Repos oder nutze Umgebungsschutzregeln. Claude Code warnt automatisch vor dieser Konfiguration.
Fazit: Claude Code als CI/CD-Architect
GitHub Actions 2026 ist weit mehr als ein simples run: npm test. Die fortgeschrittenen Features — Reusable Workflows, Matrix Strategy, OIDC und Container Jobs — ermöglichen Pipelines die skalieren, sicher sind und Teams produktiv halten.
Claude Code hilft dabei auf mehreren Ebenen: Es generiert nicht nur korrekte YAML-Syntax, sondern kennt auch die Security-Best-Practices (OIDC statt langlebige Keys), erklärt Trade-offs zwischen Ansätzen und debuggt bestehende Workflows. Der typische Arbeitsablauf: Anforderung beschreiben → Claude Code generiert Basis-Workflow → Review und Anpassung → Commit.
🔁
Reusable Workflows
Einmal schreiben, überall nutzen. Claude Code findet die richtige workflow_call-Struktur.
🧹
Matrix Strategy
Multi-OS und Multi-Version gleichzeitig testen — Claude Code optimiert include/exclude.
⚡
Caching
Richtige hashFiles()-Keys und restore-keys — bis zu 8 Minuten Ersparnis pro Build.
🔐
OIDC
Keine langlebigen Cloud-Secrets mehr. Claude Code kennt alle Provider-spezifischen Configs.
🐳
Container Jobs
PostgreSQL, Redis, Elasticsearch direkt im Workflow — mit korrekten Health-Checks.
🖥
Self-Hosted
GPU, ARM, spezielle Hardware — Claude Code kennt die Sicherheitsregeln für öffentliche Repos.
Teste Claude Code für deine CI/CD-Pipelines
Lass Claude Code deinen nächsten GitHub Actions Workflow generieren — von der Trigger-Definition bis zum OIDC-Deploy. 14 Tage kostenlos, keine Kreditkarte erforderlich.
Kostenlos starten →