Effiziente Deployments mit GitLab Runner und CI/CD

In diesem Beitrag schauen wir uns an, wie man einen GitLab Runner einrichtet und eine Continuous Integration / Continuous Delivery-Pipeline (CI/CD-Pipeline) nutzt. Damit können Änderungen an Anwendungen oder Websites automatisiert getestet, gebaut und direkt auf den Zielserver übertragen werden. Das minimiert das Fehlerrisiko und sorgt für schnelle, transparente Abläufe.

Was ist GitLab Runner und wozu dient CI/CD?

GitLab Runner ist ein Programm, das auf einem Server oder lokal läuft und die von GitLab definierten Aufgaben (Jobs) übernimmt. Diese Jobs werden in der CI/CD-Pipeline definiert und automatisieren z. B. das Testen, Kompilieren oder Deployen deines Projektes.

Einrichtung des GitLab Servers (Runner-Installation)

GitLab Runner installieren
Auf dem GitLab-Server das offizielle Installationsskript ausführen:

    curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
    sudo apt-get update
    sudo apt-get install gitlab-runner

    GitLab Runner registrieren

      • In GitLab als Admin zu Admin > CI/CD > Runners navigieren.
      • Neuen Runner erstellen (New Instance Runner).
      • „Run untagged jobs“ aktivieren und Create Runner auswählen.
      • Den generierten Befehl auf dem Server ausführen, um den Runner zu registrieren (Token wird dafür benötigt).

      Anlegen des website-Nutzers in GitLab

      Damit GitLab den Deployment-Benutzer korrekt zuordnen kann, empfiehlt es sich, einen dedizierten website-Nutzer auch in GitLab anzulegen. Dadurch erhalten Deployments eine klar zugewiesene Identität und Berechtigungsstruktur.

      Vorgehensweise:

      1. Neuen Benutzer in GitLab erstellen
        • Melde dich mit deinem Admin-Konto in GitLab an.
        • Gehe zu Admin Area > Overview > Users (oder in die Benutzerverwaltung je nach GitLab-Version).
        • Erstelle über New user einen neuen Account, z. B. website.
        • Sichere Vergabe von Passwort und/oder Passwortwechsel beim ersten Login.
      2. SSH-Schlüssel hinterlegen
        • Melde dich (oder der Nutzer selbst) als website in GitLab an.
        • Unter User Settings > SSH Keys den öffentlichen Schlüssel einfügen, der später für den Deployment-Prozess genutzt wird.
      3. Projekt-Mitgliedschaft für website-Nutzer
        • In deinem Projekt auf Settings > Members (bzw. Manage -> Members) wechseln.
        • Den Nutzer website als Maintainer hinzufügen.
        • Somit kann dieser Nutzer Code pullen, Merges durchführen und Deployments ausführen.

      Einstellungen im jeweiligen GitLab-Projekt

      CI/CD-Variablen

      • Unter Settings -> CI/CD -> Variables musst du Variablen für dein Projekt anlegen:
        • DEPLOY_KEY: Privater SSH-Schlüssel (unbedingt als “protected” / “masked” markieren).
        • SERVER_IP: IP-Adresse oder Domain des Zielservers.
        • TARGET_FOLDER: Zielverzeichnis auf dem Server.

      Projekt-Mitglieder verwalten

      • Unter Manage -> Members (oder Settings -> Members) den website-Nutzer als Maintainer hinzufügen (falls nicht schon geschehen).

      Branch- und Mitglieder-Management

      • Unter Settings -> Repository sollten Protected Branches eingerichtet werden, damit nicht versehentlich direkt auf main gepusht wird.
      • So müssen Änderungen erst in einen Feature-Branch gepusht und dann via Merge Request geprüft und gemergt werden.

      Vorbereitung des Zielservers

      Nun wird der Zielserver (etwa ein Produktions- oder Staging-System) vorbereitet, sodass dieser in der Pipeline angesprochen werden kann.

      Benutzer für Website-Deployments

      Erstelle auf dem Zielserver einen dedizierten Nutzer, z. B. website, der die Pulldienste übernimmt:

      sudo useradd -m -s /bin/bash -p "" website
      su - website

      Damit haben wir einen eigenen User, unter dem die Deployment-Schritte laufen.

      SSH-Schlüssel konfigurieren

      Im Home-Verzeichnis des website-Nutzers:

      mkdir .ssh
      cd .ssh
      # Öffentlichen (id_rsa.pub) und privaten (id_rsa) Schlüssel anlegen oder einfügen:
      nano id_rsa.pub
      nano id_rsa

      cat id_rsa.pub > authorized_keys
      chmod 700 ~/.ssh
      chmod 600 ~/.ssh/*
      exit

      Damit kann sich GitLab per SSH einloggen, wenn der in GitLab hinterlegte Schlüssel identisch ist.

      Dateiberechtigungen setzen

      sudo usermod -a -G www-data website
      sudo chown -R website:www-data /var/www/projektordner
      sudo chmod -R 775 /var/www/projektordner/storage /var/www/projektordner/bootstrap/cache

      So kann der Webserver (Gruppe www-data) die relevanten Verzeichnisse lesen/schreiben.
      Bei Laravel-Projekten sind storage und bootstrap/cache besonders wichtig (sie müssen beschreibbar sein).

      Erstmaliges Pull ausführen

      Wechsle in den Projektordner und führe testweise einen Pull durch, um den SSH-Fingerprint zu speichern:

      cd /var/www/projektordner
      git pull

      Die SSH-Abfrage mit „yes“ bestätigen.

      Aufbau der .gitlab-ci.yml-Datei

      Im Projektverzeichnis wird über die .gitlab-ci.yml definiert, welche Jobs ausgeführt werden sollen. Unten ein Beispiel für einen automatischen Deployment-Job unserer Laravel und VueJS Anwendungen:

      stages:
        - deploy
      
      deploy_production:
        stage: deploy
        only:
          - master  # Ausführung nur bei Pushes in den master-Branch (oder main)
        before_script:
          - mkdir -p ~/.ssh
          - echo "$DEPLOY_KEY" > ~/.ssh/id_rsa
          - chmod 600 ~/.ssh/id_rsa
          - echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile=/dev/null\n" > ~/.ssh/config
        script:
          # Wartungsmodus aktivieren (z.B. bei Laravel-Projekten)
          - ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan down || true"
          
          # Code vom Remote-Repository pullen und Ergebnis parsen
          - |
            PULLED_FILES=$(ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && git pull")
            echo "$PULLED_FILES"
      
            # Composer ausführen, falls composer.json aktualisiert wurde
            if echo "$PULLED_FILES" | grep -E 'composer.json'; then
              ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && composer install"
            else
              echo "No composer changes, skipping composer install"
            fi
      
            # NPM installieren, falls package.json aktualisiert wurde
            if echo "$PULLED_FILES" | grep -E 'package.json'; then
              ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && npm install"
            else
              echo "No package changes, skipping npm install"
            fi
      
            # Frontend neu bauen, wenn relevante Dateien geändert wurden
            if echo "$PULLED_FILES" | grep -E '\.vue|\.js|\.css|\.scss|\.sass|\.less|\.ts|\.jsx|\.tsx'; then
              ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && npm run build"
            else
              echo "No frontend changes, skipping compilation"
            fi
      
            # Datenbank-Migrationen anstoßen, wenn es neue Migrations gibt
            if echo "$PULLED_FILES" | grep -E 'database/migrations/'; then
              ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan migrate --force"
            else
              echo "No migrations changes, skipping migrations"
            fi
      
            # Wartungsmodus deaktivieren
            ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan up"
        after_script:
          - ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan up || true"
      

      Was passiert hier?

      • Bei jedem Merge/Pull in master (oder main) zieht GitLab den Code auf den Zielserver.
      • Wenn sich bestimmte Dateien (z. B. composer.json, package.json) geändert haben, werden automatisch die entsprechenden Installationsprozesse angestoßen (z. B. composer install, npm install).
      • Falls JS/CSS/Frontend-Dateien aktualisiert wurden, führt der Server einen Build-Prozess (npm run build) aus.
      • Bei Änderungen in database/migrations/ werden Laravel-Migrationen (php artisan migrate --force) ausgeführt.
      • Zu Beginn wird die Anwendung in den Wartungsmodus gesetzt (php artisan down), damit Benutzer keine fehlerhaften Zustände erleben. Am Ende wird sie wieder online genommen (php artisan up).

      Fazit

      Mit einer durchdachten CI/CD-Pipeline und einem GitLab Runner können Deployments stark automatisiert und abgesichert werden.

      • Anlegen des website-Nutzers in GitLab + hinterlegen der Keys.
      • Einrichtung eines dedizierten Benutzers auf dem Zielserver.
      • CI/CD-Variablen hinterlegen und Projektrechte entsprechend setzen.
      • .gitlab-ci.yml schreiben, die alle erforderlichen Deploy-Schritte abbildet.

      So wird aus jedem Merge in den Haupt-Branch automatisch ein konsistentes Update des Live-Systems oder einer Staging-Umgebung, inklusive Installation, Build-Prozess und ggf. Datenbankanpassungen.

      Solltest du Fragen haben oder tiefer in die Materie einsteigen wollen, findest du weitere Ressourcen und Blogposts unter
      blog.admin-code.de. Viel Erfolg bei deinem CI/CD-Workflow!