[ OK ]Kernel wird initialisiert...
~/im/blog
Kontakt aufnehmen

Lass uns reden

Interesse an einer Zusammenarbeit oder eine Frage? Ich bin immer offen für neue Projekte.

Kontakt aufnehmen

Vernetzen

Finden Sie mich in sozialen Medien und auf beruflichen Netzwerken.

DatenschutzerklärungNutzungsbedingungen
© 2026 Irfan MiralEntwickelt vonirfanMiral.com
StartÜber mich/LebenslaufBlogKontakt
2026-01-22• 5 Min. Lesezeit

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

DevOps GitLab CI/CD Automatisierung

Wenn ich ein kleines Projekt übernehme, das noch keine CI hat, läuft Deployment meistens so: jemand loggt sich per SSH ein, zieht den aktuellsten Commit und startet einen Dienst neu. Das funktioniert, bis es nicht mehr funktioniert, ein vergessenes npm install, ein Deploy vom falschen Branch, eine fehlende Umgebungsvariable. Die Lösung ist keine komplizierte Pipeline mit einem Dutzend Stages. Es ist eine kurze .gitlab-ci.yml, die jedes Mal dasselbe macht: bauen, testen, deployen.

Die Grundstruktur

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 Checkouts bei Repos mit langer Historie schnell, und das Caching von node_modules pro Branch sorgt dafür, dass die meisten Pipelines eine komplette Neuinstallation überspringen. Die Build-Stage erzeugt ein Artefakt, das die Deploy-Stage wiederverwendet, der Code wird also pro Pipeline-Lauf nur einmal gebaut, nicht einmal pro Stage.

Deployment über SSH

Für kleine Projekte ist ein Kubernetes-Manifest oder eine Container-Registry oft mehr Infrastruktur, als das Projekt braucht. Schlichtes rsync über SSH, ausgelöst aus der CI, deckt überraschend viele reale Deployments 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-Key liegt als maskierte, geschützte CI/CD-Variable, niemals im Repo. ssh-keyscan füllt known_hosts, damit der Job nicht an einem Host-Key-Prompt hängen bleibt, und rsync --delete hält das Remote-Verzeichnis synchron mit dem Build-Output, statt nach und nach veraltete Dateien anzusammeln.

Production bekommt einen manuellen Gate

Staging deployt bei jedem Push auf develop. Production deployt von main, aber nur, wenn jemand den Button klickt:

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

when: manual macht aus dem Job einen Button in GitLabs Pipeline-Ansicht statt etwas, das automatisch losläuft. Zusammen mit GitLabs Environments bekommt man dadurch auch eine Deploy-Historie mit Rollback per Klick auf ein vorheriges Artefakt, was bei mir mehr als einmal aus einem schiefgelaufenen Deploy eine Sache von dreißig Sekunden gemacht hat, statt Panik.

Warum das meistens reicht

Diese Pipeline führt keinen Linter aus, baut kein Docker-Image, spricht nicht mit Kubernetes. Für viele kleine Projekte ist das in Ordnung, das kann später ergänzt werden, falls das Projekt in eine Größe wächst, die es braucht. Was diese Pipeline tut, ist die drei Stellen zu entschärfen, an denen manuelle Deploys typischerweise schiefgehen: jemand vergisst, Abhängigkeiten zu installieren, jemand deployt ungetesteten Code, oder jemand deployt den falschen Branch. Eine .gitlab-ci.yml dieser Größe fängt alle drei ab, und sie ist kurz genug, dass die nächste Person, die das Projekt anfasst, sie in unter einer Minute komplett lesen kann.

VorherigerBash-Skripte, die ich auf jedem neuen Server wieder einsetzeNächster Das Ansible-Playbook, das auf jedem neuen Server läuft