GitLab CI/CD Pipeline einrichten – Schritt-für-Schritt Guide 2026

Was ist GitLab CI/CD und warum ist es 2026 noch relevant?

GitLab CI/CD ist die in GitLab integrierte Continuous-Integration- und Continuous-Deployment-Lösung, die Build-, Test- und Deployment-Prozesse direkt im Repository automatisiert. Auch 2026 gehört sie zu den meistgenutzten Pipelines weltweit, weil sie ohne externe Tools auskommt, eine starke YAML-Syntax bietet und sowohl Self-Hosted als auch SaaS funktioniert. Gerade für Teams, die Wert auf Datenschutz, europäische Server und reproduzierbare Deployments legen, ist GitLab CI/CD die erste Wahl.

Im Vergleich zu GitHub Actions, Jenkins oder CircleCI punktet GitLab vor allem mit eingebauter Container-Registry, Security-Scanning, Review-Apps und einer granularen Rollenverwaltung. Wer DevSecOps wirklich leben will, bekommt hier Static Application Security Testing (SAST), Dependency Scanning und Container Scanning fast gratis dazu.

Für hostazar.com-Kunden ergibt sich daraus ein klarer Vorteil: Ein gehosteter GitLab-Runner in einem deutschen Rechenzentrum, kombiniert mit schnellen VPS- oder Dedicated-Servern, schafft eine DSGVO-konforme Pipeline ohne Vendor-Lock-in nach US-Recht.

Voraussetzungen für deine erste Pipeline

Bevor du loslegst, brauchst du ein GitLab-Projekt (öffentlich oder privat), Schreibrechte auf die .gitlab-ci.yml und optional einen eigenen Runner, falls du Builds unabhängig von den geteilten shared runners ausführen willst. Auf deinem lokalen System sollten außerdem Git, ein SSH-Client und je nach Stack Docker oder Node.js installiert sein.

Wenn du vorhabst, Docker-Images zu bauen und in die GitLab-Registry zu pushen, lege unter Settings → CI/CD → Container Registry einen Access Token an oder nutze die eingebaute $CI_REGISTRY-Variable, die GitLab automatisch setzt. Das erspart dir manuelle Login-Schritte und hält deine Secrets sicher im Secret-Vault.

Empfehlenswert ist zudem ein zweiter Branch namens develop, in dem du Änderungen vorab testest. Production-Deployments laufen später nur über main oder getaggte Releases – so trennst du Feature-Entwicklung und Live-Betrieb sauber voneinander.

Die .gitlab-ci.yml verstehen

Die Datei .gitlab-ci.yml im Projekt-Root ist das Herzstück deiner Pipeline. Sie besteht aus globalen Schlüsselwörtern wie stages, variables, include und default, gefolgt von einzelnen jobs, die jeweils einen script-Block enthalten. Jeder Job läuft in einem frischen Container, basierend auf dem gewählten image.

Stages geben die Reihenfolge vor (z. B. build, test, deploy), während Jobs innerhalb einer Stage parallel laufen. Wenn ein Job fehlschlägt, bricht die Pipeline standardmäßig ab, sofern du nicht allow_failure: true setzt. Das macht Pipelines deterministisch und nachvollziehbar.

Ein minimales Beispiel für eine Node.js-App siehst du hier. Wir verwenden Caching für node_modules und publizieren das gebaute Verzeichnis als Artifact, damit es in der nächsten Stage verwendet werden kann.

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

variables:
  NODE_VERSION: "20"
  APP_DIR: "dist"

default:
  image: node:${NODE_VERSION}-alpine
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/

build:
  stage: build
  script:
    - npm ci --prefer-offline
    - npm run build
  artifacts:
    paths:
      - ${APP_DIR}
    expire_in: 1 hour

test:
  stage: test
  script:
    - npm ci --prefer-offline
    - npm run lint
    - npm run test:unit
  needs: ["build"]

deploy:
  stage: deploy
  script:
    - echo "Deploying ${APP_DIR} to production"
  environment:
    name: production
    url: https://example.com
  rules:
    - if: $CI_COMMIT_TAG
  when: manual

Stages, Jobs und Parallelisierung clever nutzen

Eine ausgewachsene Pipeline lebt davon, dass Stages sequentiell laufen, Jobs innerhalb einer Stage aber parallel. So kannst du Unit-Tests, Linting, Security-Scans und Build-Tests gleichzeitig ausführen und die Gesamtlaufzeit drastisch reduzieren. Mit dem Schlüsselwort needs umgehst du die strikte Stage-Reihenfolge und startest Jobs schon dann, wenn ihre konkreten Abhängigkeiten fertig sind – das nennt GitLab DAG (Directed Acyclic Graph).

Gerade bei monorepos mit vielen Paketen ist das ein riesiger Performance-Gewinn. Du kannst pro Paket einen Job anlegen, jeder Job hat seine eigenen needs und dependencies. Das verhindert, dass eine kleine Änderung an einem Package den kompletten Test-Suite-Lauf auslöst.

Achte darauf, jeden Job mit aussagekräftigen tags zu versehen, wenn du mehrere Runner mit unterschiedlichen Labels hast (z. B. linux, docker, gpu, windows). GitLab matched diese Tags und wählt den passenden Runner aus. So bekommt ein GPU-Job automatisch deinen teuren GPU-Runner, ein normaler Lint-Job aber den günstigen shared Runner.

Variablen, Secrets und Protected Branches

Sensible Daten wie API-Keys, Datenbank-Passwörter oder SSH-Keys haben in einer .gitlab-ci.yml nichts zu suchen. Stattdessen speicherst du sie als CI/CD-Variablen im Projekt-, Gruppen- oder Instance-Settings. Du kannst sie als Protected (nur für Protected Branches/Tags sichtbar) und Masked (im Log ausgeblendet) markieren.

Für komplexere Szenarien empfiehlt sich HashiCorp Vault, das GitLab nativ integriert. Über vault://path/to/secret/key referenzierst du Secrets direkt in deinem Script – GitLab holt sie zum Job-Start ab, ohne sie im Runner-Image zu persistieren.

Typische Variablen für eine Production-Pipeline sind in der folgenden Tabelle zusammengefasst. Passe sie an dein Projekt an und ergänze pro Environment eigene Werte.

VariableTypSichtbarkeitVerwendung
CI_REGISTRY_USERAutoProtectedLogin GitLab Container Registry
CI_REGISTRY_PASSWORDAutoProtectedLogin GitLab Container Registry
SSH_PRIVATE_KEYFileProtected + MaskedDeploy via SSH
DATABASE_URLSecretProtectedMigration- und Test-Jobs
SLACK_WEBHOOKSecretMaskedPipeline-Notifications

Caching und Artifacts – die heimlichen Performance-Helden

Wer viel mit npm, pip, maven oder composer arbeitet, sollte Caching unbedingt aktivieren. GitLab legt damit kompilierte Pakete in einem verteilten Cache ab und stellt sie in nachfolgenden Jobs schnell wieder her. Cache-Keys kannst du auf $CI_COMMIT_REF_SLUG, $CI_PIPELINE_ID oder eine Kombination aus Datei-Hash und Branch setzen.

Artifacts sind im Gegensatz zum Cache dateibasierte Outputs, die explizit weitergereicht werden (z. B. ein JAR, ein Docker-Image-Tar, ein Test-Report). Sie haben ein Verfallsdatum (expire_in) und können in der Merge-Request-View heruntergeladen werden, was das Debugging erheblich erleichtert.

Beispiel für einen Build-Job, der sowohl Cache als auch Artifact nutzt:

build:
  stage: build
  image: maven:3.9-eclipse-temurin-21
  cache:
    key: maven-${CI_COMMIT_REF_SLUG}
    paths:
      - .m2/repository
  script:
    - mvn -B clean package -DskipTests
  artifacts:
    name: "app-${CI_COMMIT_SHORT_SHA}"
    paths:
      - target/*.jar
    expire_in: 7 days
    reports:
      junit: target/surefire-reports/TEST-*.xml

Docker in Docker, Kaniko und Container-Deployments

Wenn du innerhalb deines Jobs ein Docker-Image bauen willst, hast du mehrere Optionen. Die klassische Variante docker:dind läuft als --privileged Container, was bei shared Runnern oft deaktiviert ist. Sicherer und schneller ist kaniko, das Images ohne Docker-Daemon baut – perfekt für unprivileged Runner.

Eine moderne GitLab-CI/CD-Pipeline baut das Image, scannt es mit Trivy, schiebt es in die eigene Registry und triggert anschließend ein Deployment auf einem Kubernetes-Cluster oder einem klassischen VPS. Hier ein bewährtes Snippet, das du in eigene Pipelines übernehmen kannst.

docker-build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.21.0-debug
    entrypoint: [""]
  script:
    - /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
      --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
      --cache=true
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Deployment-Strategien: Rolling, Blue/Green und Canary

Eine gute Pipeline endet nicht mit kubectl apply -f. Für produktive Systeme willst du Zero-Downtime, automatisches Rollback und eine möglichst geringe Fehlerrate. GitLab unterstützt dafür Environments, Deploy Boards und Deploy Keys. Kombiniert mit Review-Apps bekommt jede Merge-Request eine eigene URL, was QA-Teams das Leben enorm erleichtert.

Strategien im Überblick:

Auf einem VPS ohne Kubernetes erreichst du Blue/Green mit zwei Reverse-Proxy-Backends und einem einfachen switch-Script, das per SSH die aktive Site tauscht. Für die meisten kleinen bis mittelgroßen Projekte ist das vollkommen ausreichend.

Security und Compliance direkt in der Pipeline

DevSecOps bedeutet, Security-Tests nicht am Ende, sondern in jeder Stage mitlaufen zu lassen. GitLab liefert dafür eingebaute Templates, die du per include einbindest. SAST prüft deinen Quellcode, Dependency Scanning schaut auf bekannte CVEs in package.json oder requirements.txt, Container Scanning untersucht das gebaute Image.

Ein vollständiger Compliance-Stack könnte so aussehen:

include:
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Dependency-Scanning.gitlab-ci.yml
  - template: Jobs/Container-Scanning.gitlab-ci.yml
  - template: Jobs/Secret-Detection.gitlab-ci.yml
  - template: Jobs/IaC-Scanning.gitlab-ci.yml

Die Ergebnisse landen im Merge-Request-Widget und blockieren Merges, wenn der Schweregrad Critical überschritten wird. So erzwingst du Security-Gates, ohne den Flow auszubremsen.

Eigene Runner betreiben – wann und wie

Shared Runner sind super, um schnell zu starten, haben aber Limitierungen bei GPU, großen Caches oder speziellen Tools. Ein eigener Runner ist schnell aufgesetzt: gitlab-runner register, Token aus den Projekt- oder Group-Settings holen, Executor wählen (am häufigsten docker), fertig. Auf einem hostazar-VPS mit 4 vCPU, 8 GB RAM und 100 GB NVMe läuft ein kleiner Runner-Tarif problemlos für 5–10 Projekte.

Für produktive Setups empfehlen wir den docker+machine oder autoscaler-Modus, der Jobs dynamisch auf frischen Containern startet und sie nach dem Job wieder zerstört. Das hält die Umgebung sauber und verhindert State-Leaks zwischen Builds.

Konfigurationsbeispiel für einen privaten Runner mit Cache-Server:

concurrent = 4
check_interval = 0
[[runners]]
  name = "hostazar-runner-01"
  url = "https://gitlab.com/"
  token = "REDACTED-RUNNER-TOKEN"
  executor = "docker"
  [runners.docker]
    image = "alpine:3.20"
    privileged = false
    pull_policy = "if-not-present"
    volumes = ["/cache", "/etc/ssl/certs:/etc/ssl/certs:ro"]
  [runners.cache]
    Type = "s3"
    [runners.cache.s3]
      ServerAddress = "s3.eu-central-1.hostazar.com"
      AccessKey = "REDACTED"
      SecretKey = "REDACTED"
      BucketName = "gitlab-runner-cache"

Best Practices und typische Fehlerquellen

Die häufigsten Stolperfallen in GitLab-CI/CD-Pipelines sind fehlende Caches,

Docker Compose auf dem VPS: Webserver, Datenbank & Reverse Proxy richtig betreiben
DevOps 02. June 2026 10 Min

Docker Compose auf dem VPS: Webserver, Datenbank & Reverse Proxy richtig betreiben

Praxisnaher Guide für Docker Compose auf dem VPS: Webserver, Datenbank, Reverse Proxy, TLS, Backups, Updates und Sicherheit richtig planen und betreiben.