Zum Inhalt springen
DevOps

GitLab CI vs GitHub Actions: Pipeline-Architekturen im Vergleich

11 min Lesezeit
CI/CD GitLab GitHub

Wer “GitLab CI vs GitHub Actions” sucht, findet Feature-Vergleichstabellen. Haekchen links, Haekchen rechts, am Ende steht überall “beide können alles”. Das stimmt — und ist trotzdem nutzlos. Die eigentliche Frage ist nicht, was die Systeme können, sondern wie sie dich dazu bringen, Pipelines zu denken. Und da unterscheiden sich GitLab CI und GitHub Actions fundamental.

Pipeline-Philosophie: Deklarativ vs. Event-Driven

GitLab CI denkt in Stages. Eine Pipeline ist eine geordnete Abfolge von Phasen: build, test, deploy. Jobs innerhalb einer Stage laufen parallel, Stages selbst sequenziell. Das Modell ist deklarativ — du beschreibst den Endzustand, nicht den Kontrollfluss.

GitHub Actions denkt in Events. Ein Workflow reagiert auf ein Ereignis: Push, Pull Request, Schedule, manueller Dispatch. Der Workflow definiert Jobs, Jobs definieren Steps. Die Beziehungen zwischen Jobs sind explizit über needs verdrahtet — es gibt kein implizites Stage-Modell.

Das klingt nach einem kleinen Unterschied. In der Praxis praegt es den gesamten Pipeline-Aufbau:

  • GitLab: Du definierst einmal, was in welcher Reihenfolge passiert. Neue Jobs ordnen sich in bestehende Stages ein. Das funktioniert gut, solange die Pipeline linear ist.
  • GitHub Actions: Du definierst Reaktionen auf Events. Komplexe Abhängigkeitsgraphen sind natürlich, aber du musst sie explizit modellieren.

Runner-Architektur im Vergleich

Beide Systeme trennen Orchestrierung von Ausführung. Die Plattform plant, der Runner führt aus. Aber die Details unterscheiden sich.

GitLab Runner ist ein einzelnes Binary, das verschiedene Executors unterstützt: Shell, Docker, Kubernetes, VirtualBox, SSH. Du registrierst einen Runner an einer GitLab-Instanz und weist ihm Tags zu. Jobs waehlen Runner über Tags aus. Das Tagging-System ist simpel und mächtig — ein Runner kann mehrere Tags haben, ein Job kann mehrere Tags fordern.

GitHub Actions Runner läuft entweder als GitHub-hosted Runner (vorkonfigurierte VMs) oder als Self-hosted Runner. Die Zuweisung erfolgt über Labels, konzeptionell aehnlich wie GitLab-Tags. GitHub-hosted Runner sind der Standardfall und für die meisten Projekte ausreichend. Der Self-hosted Runner ist ein .NET-Prozess, der sich als Listener an die GitHub API hängt.

Der größte praktische Unterschied: GitLab bietet mit dem Docker-Executor und dem Kubernetes-Executor zwei Optionen, die Container-basierte Builds ohne Docker-in-Docker ermöglichen. Bei GitHub Actions musst du entweder die vorkonfigurierten VMs nehmen oder dich um die Container-Runtime auf Self-hosted Runnern selbst kuemmern.

Pipeline-Definition im Vergleich

Schauen wir uns eine typische Pipeline an — Build, Test, Deploy — in beiden Systemen.

.gitlab-ci.yml -- Klassische Pipeline yaml
stages:
  - build
  - test
  - deploy

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

test:
  stage: test
  image: $DOCKER_IMAGE
  script:
    - npm run test:unit
    - npm run test:integration
  coverage: '/Statements\s*:\s*(\d+\.?\d*)%/'

deploy:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/app app=$DOCKER_IMAGE
  environment:
    name: production
    url: https://example.com
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
.github/workflows/ci.yml -- Äquivalent in GitHub Actions yaml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  DOCKER_IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ env.DOCKER_IMAGE }}

  test:
    needs: build
    runs-on: ubuntu-latest
    container:
      image: ${{ env.DOCKER_IMAGE }}
    steps:
      - run: npm run test:unit
      - run: npm run test:integration

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBECONFIG }}
      - run: kubectl set image deployment/app app=${{ env.DOCKER_IMAGE }}

Auf den ersten Blick aehnlich. Aber beachte die Unterschiede: GitLab braucht services: [docker:dind] für Docker Builds. GitHub Actions delegiert an vorgefertigte Actions (docker/build-push-action). GitLab definiert Abhängigkeiten implizit über Stages, GitHub Actions explizit über needs.

Wiederverwendbarkeit: Templates vs. Composite Actions

Hier trennt sich die Spreu vom Weizen in größeren Organisationen.

GitLab bietet drei Mechanismen:

  • include: YAML-Dateien einbinden, lokal oder remote
  • extends: Jobs von Templates erben lassen
  • !reference: Einzelne Keys aus anderen Jobs referenzieren

GitHub Actions bietet ebenfalls drei Wege:

  • Reusable Workflows: Ganze Workflows als aufrufbare Einheiten (workflow_call)
  • Composite Actions: Mehrere Steps als eine Action buendeln
  • Starter Workflows: Org-weite Templates für neue Repositories
GitLab CI -- Template mit include und extends yaml
# templates/docker-build.yml (in einem zentralen Repository)
.docker-build:
  image: docker:24
  services:
    - docker:24-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

# .gitlab-ci.yml im Projekt
include:
  - project: 'devops/ci-templates'
    ref: main
    file: '/templates/docker-build.yml'

build:
  extends: .docker-build
  variables:
    DOCKER_BUILDKIT: "1"
GitHub Actions -- Reusable Workflow yaml
# .github/workflows/docker-build.yml (im Template-Repository)
name: Docker Build
on:
  workflow_call:
    inputs:
      image-name:
        required: true
        type: string
    secrets:
      registry-password:
        required: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ inputs.image-name }}:${{ github.sha }}

# .github/workflows/ci.yml im Projekt
name: CI
on: [push]
jobs:
  build:
    uses: org/ci-templates/.github/workflows/docker-build.yml@main
    with:
      image-name: ghcr.io/${{ github.repository }}
    secrets:
      registry-password: ${{ secrets.GITHUB_TOKEN }}

GitLabs extends-System ist flexibler — du kannst einzelne Keys überschreiben, Arrays mergen, beliebig tief verschachteln. Das ist mächtig, führt aber in großen Organisationen zu YAML-Konstrukten, die niemand mehr versteht. GitHub Actions’ Reusable Workflows sind eingeschraenkter, aber expliziter: Inputs und Outputs sind klar definiert.

Security: Secrets, OIDC und Protected Environments

Beide Systeme bieten verschlüsselte Secrets auf Projekt- und Organisationsebene. Die Unterschiede liegen im Detail:

GitLab trennt Variablen nach Umgebungen und schützt sie über Protected Branches und Protected Tags. Masked Variables werden in Logs geschwuerzt. Seit GitLab 16 gibt es native OIDC-Token für Cloud-Provider-Authentifizierung — keine langlebigen Credentials mehr für AWS, GCP oder Azure.

GitHub Actions bietet Environment Secrets, Repository Secrets und Organization Secrets. Environments können Reviewer-Approvals und Wait Timer haben. OIDC ist seit 2022 verfügbar und gut dokumentiert.

Mono-Repo Support und Pipeline-Performance

Bei Mono-Repos zeigt sich, wie gut ein CI-System skaliert.

GitLab bietet rules:changes — Jobs laufen nur, wenn sich Dateien in bestimmten Pfaden geändert haben. Mit needs und dem DAG-Modus (Directed Acyclic Graph) lassen sich Abhängigkeiten zwischen Jobs unabhängig von Stages definieren. Das Parent-Child-Pipeline-Feature erlaubt es, dynamisch Sub-Pipelines zu erzeugen.

GitHub Actions hat paths und paths-ignore auf Workflow-Ebene, aber nicht auf Job-Ebene. Für feinere Kontrolle brauchst du Workarounds: dorny/paths-filter oder manuelle Checks mit git diff. Dafuer ist die Parallelisierung über matrix elegant — du definierst eine Matrix von Parametern, und GitHub Actions erzeugt automatisch einen Job pro Kombination.

Die Pipeline-Performance wird bei beiden Systemen von Caching und Artefakt-Management dominiert. GitLab hat einen integrierten Cache pro Runner, GitHub Actions nutzt actions/cache mit einem zentralen Cache-Backend. In der Praxis sind beide aehnlich schnell, solange du dich um Caching kuemmerst.

Self-hosted Runner: Setup, Kosten, Wartung

Sobald GitHub-hosted Runner oder GitLab SaaS-Runner nicht mehr reichen — spezielle Hardware, Compliance-Anforderungen, Kostenoptimierung — musst du selbst Runner betreiben.

GitLab Runner ist ein Go-Binary. Installation auf einem Linux-Server dauert fuenf Minuten. Die Konfiguration erfolgt über config.toml, der Kubernetes-Executor macht Auto-Scaling trivial. Der Runner unterstützt mehrere parallele Jobs, Cache-Verzeichnisse und Custom-Executor-Plugins. Für größere Setups gibt es den GitLab Runner Operator für Kubernetes.

GitHub Actions Self-hosted Runner ist ein .NET-Prozess. Die Ersteinrichtung ist einfach (Anleitung direkt in der GitHub UI), aber Skalierung erfordert zusätzliche Tooling. Actions Runner Controller (ARC) ist die offizielle Kubernetes-Lösung für Auto-Scaling. Runner Groups ermöglichen die Organisation nach Teams oder Umgebungen.

Kostenvergleich: GitLab SaaS bietet 400 CI-Minuten im Free-Tier, GitHub Actions 2.000 Minuten. Bei Self-hosted Runnern zahlst du in beiden Faellen nur die Infrastruktur. Der Unterschied liegt im Verwaltungsaufwand: GitLab Runner ist ausgereifter und bietet mehr Executor-Optionen. ARC für GitHub Actions hat sich seit der offiziellen Übernahme durch GitHub aber deutlich verbessert.

Migration zwischen den Systemen

Migration ist möglich, aber nie ein reines Syntax-Mapping. Die Konzepte unterscheiden sich genug, dass du Pipeline-Logik neu denken musst.

Von GitLab zu GitHub Actions:

  • stages lösen sich auf — du modellierst Abhängigkeiten explizit mit needs
  • include/extends werden zu Reusable Workflows und Composite Actions
  • services (z.B. Postgres für Tests) werden zu Service Containers
  • environment mappt direkt auf GitHub Environments
  • CI/CD-Variablen müssen in GitHub Secrets und Variables uebertragen werden

Von GitHub Actions zu GitLab CI:

  • Actions aus dem Marketplace haben kein Äquivalent — du musst die Logik in Scripts oder eigene CI-Templates portieren
  • matrix wird zu parallel:matrix in GitLab
  • Workflow-Trigger werden zu rules mit Pipeline-Source-Bedingungen
  • Composite Actions werden zu Hidden Jobs mit extends

Entscheidungshilfe: Wann welches System

Die Entscheidung ist selten rein technisch. Aber hier sind die Szenarien, in denen ein System klar vorne liegt:

GitLab CI waehlen, wenn:

  • Du GitLab bereits als Plattform nutzt (SCM, Issues, Registry — alles integriert)
  • Komplexe Mono-Repo-Setups mit Parent-Child-Pipelines benötigt werden
  • Self-hosted Runner mit Kubernetes-Executor im Einsatz sind
  • Die Organisation ein zentrales Template-Repository mit include/extends pflegt
  • Compliance-Anforderungen eine Single-Platform-Lösung erfordern

GitHub Actions waehlen, wenn:

  • Die Codebasis auf GitHub liegt (offensichtlich, aber relevant)
  • Das Team den Marketplace aktiv nutzt — vorgefertigte Actions sparen Tage
  • Event-getriebene Workflows über CI/CD hinausgehen (Issue-Automatisierung, Releases, Dependabot)
  • Open-Source-Projekte mit Community-Beiträgen verwaltet werden
  • Die Organisation GitHub Enterprise mit GHES oder GHEC einsetzt

Egal welches System: Investiere in Pipeline-Templates, Caching-Strategien und Runner-Infrastruktur. Die Pipeline, die niemand pflegt, ist in beiden Systemen gleich schlecht.

Fazit

GitLab CI und GitHub Actions sind beide ausgereift und leistungsfähig. Der Unterschied liegt nicht in Features, sondern in der Architektur: deklarative Stages vs. event-getriebene Workflows. Beide Modelle haben Stärken, und das “bessere” System ist das, das zu eurer bestehenden Infrastruktur und eurem mentalen Modell passt.

Die wichtigste Erkenntnis nach Jahren mit beiden Systemen: Die Qualität einer CI/CD-Pipeline hängt weniger vom Tool ab als von der Disziplin des Teams. Schnelle Feedback-Loops, deterministische Builds, sauberes Caching, sinnvolle Parallelisierung — das sind die Hebel, die den Unterschied machen. Nicht das Logo im Tab.