[ OK ]Initialisiere Kernel...
~/im/blog
Beauftragen

Lass uns reden

Gibt es ein Infrastrukturproblem oder wird Unterstützung bei einem Projekt benötigt? Ich stehe für neue Aufgaben zur Verfügung.

Kontakt aufnehmen

Netzwerke

Hier bin ich online zu finden.

© 2026 Irfan Miral. Alle Rechte vorbehalten.Entwickelt vonIrfan Miral
DatenschutzerklärungAGB
StartDiensteLebenslaufBlogKontaktTools
2026-01-22• 5 Min. Lesezeit

Eine GitLab CI/CD-Pipeline, die für die meisten kleinen Projekte reicht

DevOps GitLab CI/CD Automation

Werbung

Wenn ich ein kleines Projekt übernehme, das noch überhaupt kein CI/CD hat, bedeutet Deployment meistens: Jemand verbindet sich per SSH, zieht den neuesten Commit und startet einen Service von Hand neu.

Das funktioniert genau so lange, bis es nicht mehr funktioniert. Ein vergessenes npm install, ein Deploy vom falschen Branch oder eine fehlende Umgebungsvariable reichen völlig aus, um die Live-Umgebung lahmzulegen. Die Lösung dafür ist keine überkomplexe Pipeline mit einem Dutzend Custom-Stages. Die Lösung ist eine kurze .gitlab-ci.yml, die extrem zuverlässig exakt dieselben drei Dinge tut: Build, Test und Deploy.

Der Aufbau

stages:
  - build
  - test
  - deploy

variables:
  GIT_DEPTH: 1

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/

build:
  stage: build
  image: node:20
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm run test -- --ci

GIT_DEPTH: 1 hält Git-Checkouts auch bei Repositories mit ewig langer Historie unglaublich schnell. Das branch-basierte Caching von node_modules sorgt dafür, dass die meisten Pipeline-Durchläufe eine komplette NPM-Neuinstallation überspringen können. Die Build-Stage erzeugt ein kompiliertes Artifact, das die Deploy-Stage anschließend wiederverwendet. Das garantiert, dass der Code pro Pipeline-Lauf nur ein einziges Mal gebaut wird und nicht in jeder Stage erneut.

Deploying über SSH

Für kleine Projekte ist das Aufsetzen eines Kubernetes-Manifests oder einer Container-Registry oft deutlich mehr Infrastruktur-Overhead, als das Projekt überhaupt braucht. Simples rsync über SSH, getriggert aus der CI, deckt eine erstaunlich große Menge an echten Deployments sicher ab:

deploy_staging:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client rsync
    - eval $(ssh-agent -s)
    - echo "$STAGING_SSH_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - ssh-keyscan -H "$STAGING_HOST" >> ~/.ssh/known_hosts
  script:
    - rsync -az --delete dist/ deploy@$STAGING_HOST:/var/www/staging/
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

Der SSH-Schlüssel liegt streng getrennt in einer maskierten, geschützten CI/CD-Variablen, niemals im Repository selbst. Der ssh-keyscan-Befehl füllt die known_hosts-Datei, damit der Pipeline-Job nicht endlos an einer Host-Key-Bestätigung hängen bleibt. Zu guter Letzt sorgt rsync --delete dafür, dass das Remote-Verzeichnis exakt mit dem Build-Output synchronisiert wird, anstatt über die Zeit langsam mit Dateileichen vollzumüllen.

Ein manuelles Gate für Production

Staging wird bei jedem Push auf den develop-Branch automatisch deployt. Production deployt vom main-Branch, aber ausschließlich dann, wenn jemand explizit auf den Knopf drückt:

deploy_production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client rsync
    - eval $(ssh-agent -s)
    - echo "$PRODUCTION_SSH_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - ssh-keyscan -H "$PRODUCTION_HOST" >> ~/.ssh/known_hosts
  script:
    - rsync -az --delete dist/ deploy@$PRODUCTION_HOST:/var/www/production/
  environment:
    name: production
    url: https://example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  when: manual

Das when: manual verwandelt den Job in der Pipeline-Ansicht von GitLab in einen echten Button, anstatt ein Skript zu sein, das automatisch abfeuert. In Kombination mit dem Environment-Tracking von GitLab erhält man so außerdem eine vollständige Deployment-Historie mit 1-Klick-Rollback auf jedes beliebige vorherige Artifact. Dieser Rollback-Button hat schon mehr als einmal ein fehlgeschlagenes Deployment in einen 30-Sekunden-Fix verwandelt, anstatt in absolute Panik auszuarten.

Warum das fast immer ausreicht

Diese Pipeline lässt keinen Linter laufen. Sie baut kein Docker-Image. Sie spricht nicht mit Kubernetes. Und für einen massiven Anteil kleinerer Projekte ist das völlig in Ordnung. Diese Dinge können jederzeit später ergänzt werden, falls das Projekt wächst und sie wirklich benötigt.

Was diese Pipeline jedoch sofort leistet: Sie eliminiert dauerhaft die drei typischen Fehlerquellen manueller Deployments. Jemand vergisst Abhängigkeiten zu installieren, jemand deployt ungetesteten Code oder jemand bringt den falschen Branch live. Eine .gitlab-ci.yml dieser Größe fängt alle drei Fälle sicher ab. Und sie ist kurz genug, dass der nächste Entwickler, der das Projekt anfasst, den kompletten Deployment-Flow in unter einer Minute lesen und verstehen kann.

Werbung

Brauchen Sie hierbei Hilfe?

Wenn Sie das Thema Containerisierung & Automatisierung lieber abgeben möchten – genau das mache ich beruflich.

Kontakt aufnehmen
VorherigerBash-Skripte, die ich auf jedem neuen Server wiederverwendeNächster Das Ansible-Playbook für jeden neuen Server