CI/CD
.yml
GitLab · CI/CD · Magento · Secrets
CI/CD Variables in GitLab richtig anlegen
SSH, Composer Auth, ENV und Secrets sicher verwalten

CI/CD Variables sind der Konfigurationsvertrag jeder GitLab-Pipeline. Wer SSH-Keys, Composer-Auth-Token und ENV-Dateien falsch hinterlegt, riskiert unsichere Secrets, gebrochene Builds und unklare Umgebungstrennung. Dieser Artikel zeigt, wie Variables in GitLab korrekt angelegt, gescopedt und in Pipelines sicher konsumiert werden.

12 Min. Lesezeit Protected · Masked · Environment Scope GitLab 16.x · Magento 2.4.x

1. Wozu CI/CD Variables – und was sie nicht sind

CI/CD Variables in GitLab sind der Mechanismus, um Konfigurationswerte, Zugangsdaten und Secrets aus dem Repository herauszuhalten und trotzdem in Pipelines verfügbar zu machen. Sie ersetzen keine vollständige Secret-Management-Lösung wie HashiCorp Vault, aber für die meisten Magento-Deployment-Teams sind sie der richtige Ort für SSH-Keys, Composer-Zugangsdaten, Datenbank-Passwörter und Pfadvariablen. Das Prinzip ist klar: Was sich zwischen Umgebungen unterscheidet oder geheim bleiben muss, gehört in eine Variable – nicht in den Code.

Was CI/CD Variables nicht leisten: Sie sind kein Ersatz für strukturiertes Secrets-Management bei Dutzenden von Services oder hochsensiblen Credentials mit Rotation. Für Magento-Projekte in typischer Teamgröße decken sie den Bedarf aber vollständig ab, wenn Scopes, Protected- und Masked-Flags konsequent eingesetzt werden. Ein häufiger Fehler ist es, Variables als bloße Umgebungsvariablen zu behandeln, ohne auf Sichtbarkeit, Scope und Maskierung zu achten. Das schafft Sicherheitslücken, die im Alltag oft erst dann auffallen, wenn ein Staging-Secret versehentlich in einem Production-Build auftaucht.

2. Variable-Typen: Protected, Masked und File

Protected Variables sind nur in Pipelines sichtbar, die auf protected Branches oder Tags laufen. Das ist der primäre Schutz vor unbeabsichtigtem Secrets-Abfluss: Ein Feature-Branch kann nicht auf Production-Credentials zugreifen, weil der Branch nicht protected ist. Für alle Production-spezifischen Variablen – SSH-Keys zum Production-Server, Datenbank-Passwörter, API-Keys – ist das Protected-Flag Pflicht. Masked Variables werden in Job-Logs unkenntlich gemacht. Die Einschränkung: Der Wert darf keine Zeilenumbrüche enthalten und muss mindestens acht Zeichen haben. Für mehrzeilige Werte wie SSH Private Keys greift Masking daher nicht vollständig.

Der dritte Typ ist File Variables. Statt des Werts direkt in der Umgebungsvariable wird der Inhalt in eine temporäre Datei auf dem Runner geschrieben, und die Variable enthält den Pfad zu dieser Datei. Das ist die richtige Wahl für SSH Private Keys, auth.json-Dateien und env.php-Inhalte – denn viele Tools erwarten einen Dateipfad statt des Inhalts direkt als Variable. Wer einen SSH-Key als normale Variable statt als File Variable anlegt, muss im Job-Script selbst das Schreiben in eine Datei übernehmen – was fehleranfälliger ist. Die Kombination aus Protected und File ist für die meisten kritischen Credentials die robusteste Wahl.

# Example: .gitlab-ci.yml — consuming CI/CD Variables correctly
variables:
  # Pipeline-level defaults (non-secret)
  DEPLOY_PATH: "/var/www/magento"
  RELEASE_RETENTION: "5"
  GIT_STRATEGY: fetch

deploy:production:
  stage: deploy
  environment: production
  only:
    - tags
  before_script:
    # SSH_PRIVATE_KEY is a File Variable — $SSH_PRIVATE_KEY contains the file path
    - chmod 600 "$SSH_PRIVATE_KEY"
    - eval "$(ssh-agent -s)"
    - ssh-add "$SSH_PRIVATE_KEY"
    # Write known hosts from variable
    - mkdir -p ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - rsync -az --delete ./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/releases/$CI_PIPELINE_ID/"
    - ssh "$DEPLOY_USER@$DEPLOY_HOST" "ln -sfn $DEPLOY_PATH/releases/$CI_PIPELINE_ID $DEPLOY_PATH/current"

3. Environment Scope: Staging und Production trennen

Der Environment Scope ist das wichtigste Werkzeug, um sicherzustellen, dass Staging-Credentials nie in einem Production-Build landen und umgekehrt. Jede Variable kann auf einen bestimmten Environment-Namen oder ein Wildcard-Muster beschränkt werden. Eine Variable mit Scope production ist nur in Jobs sichtbar, deren environment:-Schlüssel auf diesen Namen verweist. Scope staging* mit Wildcard deckt alle Staging-Umgebungen ab.

Das bedeutet in der Praxis: Zwei Variablen mit demselben Namen – einmal mit Scope staging, einmal mit Scope production – halten die Umgebungen sauber getrennt, ohne dass das Pipeline-YAML angepasst werden muss. Der Build-Job sieht immer die für seine Umgebung passende Variable. Ein Fehler, den Teams regelmäßig machen: Sie legen eine Variable mit Scope * (alle Umgebungen) an und merken nicht, dass damit auch Feature-Branches auf dieselben Credentials zugreifen können. Wer dieses Risiko vermeiden will, muss für jeden echten Environment-Wert einen eigenen Scope definieren – auch wenn das anfangs mehr Pflegeaufwand bedeutet.

4. SSH Private Key sicher in GitLab hinterlegen

Der SSH Private Key ist eine der sensibelsten Variablen in einer Magento-Pipeline: Er erlaubt direkten Serverzugriff. Die korrekte Vorgehensweise: Den Key als File Variable mit gesetztem Protected-Flag anlegen. Der öffentliche Schlüssel kommt auf den Zielserver in ~/.ssh/authorized_keys des Deployment-Users. Der Fingerprint des Zielservers wird einmalig manuell geprüft und als separate Variable SSH_KNOWN_HOSTS hinterlegt. Wer StrictHostKeyChecking=no in die Pipeline schreibt, umgeht diesen Schutz komplett – das ist ein Sicherheitsproblem, kein Komfort.

Der Deployment-User auf dem Zielserver sollte ein dedizierter Account ohne sudo-Rechte sein, dessen Zugriff auf das Release-Verzeichnis beschränkt ist. Das Prinzip des least privilege gilt auch hier: Die Pipeline braucht keinen Root-Zugriff. Ein weiteres häufiges Problem ist das Anlegen des SSH-Keys als normale Variable statt als File Variable. Das führt dazu, dass der Zeilenumbruch am Ende des privaten Schlüssels verloren geht, was den Key ungültig macht – ein Fehler, der im Job-Log als kryptische SSH-Fehlermeldung erscheint und unnötig viel Debugging-Zeit kostet.

5. Composer Auth: Magento-Marketplace und private Pakete

Magento-Projekte benötigen Composer-Credentials für den Magento Marketplace (repo.magento.com) und häufig für weitere private Paketquellen wie Hyvä Themes oder andere kommerzielle Erweiterungen. Diese Zugangsdaten gehören als COMPOSER_AUTH-Variable in GitLab – als JSON-Objekt im Format, das Composer direkt versteht. Im Build-Job wird die Variable in die Datei ~/.composer/auth.json oder über die Umgebungsvariable COMPOSER_AUTH direkt übergeben.

Das Ablegen von Composer-Credentials im Repository als auth.json ist ein klassischer Fehler, der in öffentlichen Repositories zu sofortigen Account-Compromittierungen führt und in privaten Repositories unnötige Credentials-Rotations erzwingt bei jedem Entwickler-Offboarding. Die GitLab-Variable ist der richtige Ort: Sie ist nicht im Repository-History sichtbar, lässt sich pro Umgebung scopen und kann rotiiert werden, ohne den Code zu berühren. Für CI-Systeme, die mehrere Magento-Projekte verwalten, bieten sich Group-Level Variables an, die für alle Projekte der Gruppe verfügbar sind.

# Build job consuming COMPOSER_AUTH as environment variable
build:composer:
  stage: build
  image: php:8.4-cli
  variables:
    # COMPOSER_AUTH is set as a GitLab CI/CD Variable (masked, protected)
    # Format: {"http-basic": {"repo.magento.com": {"username": "...", "password": "..."}}}
    COMPOSER_CACHE_DIR: ".cache/composer"
  cache:
    key: composer-$CI_COMMIT_REF_SLUG
    paths:
      - .cache/composer/
  script:
    # Composer reads COMPOSER_AUTH env variable automatically
    - composer install --no-dev --prefer-dist --no-interaction --no-ansi
    - composer dump-autoload --optimize --no-dev
  artifacts:
    paths:
      - vendor/
    expire_in: 1 day

6. ENV-Datei und app/etc/env.php als Variable

Die app/etc/env.php von Magento enthält Datenbankverbindungen, Redis-Konfiguration, Session-Settings und Crypt-Keys – alles hochsensibel und umgebungsspezifisch. Diese Datei darf niemals im Repository liegen. In einem GitLab-Deployment-Modell gibt es zwei verbreitete Ansätze: Entweder liegt die Datei dauerhaft im Shared-Verzeichnis auf dem Server und wird beim Release per Symlink eingebunden, oder der Inhalt wird als File Variable in GitLab hinterlegt und bei jedem Deploy auf den Server übertragen.

Der erste Ansatz ist für die meisten Teams der robustere, weil er die Datei vom CI-System entkoppelt und keine env.php-Inhalte durch Runner-Prozesse fließen. Der zweite Ansatz ist sinnvoll, wenn die env.php per Pipeline vollständig kontrolliert werden soll – etwa bei Blue-Green-Deployments mit unterschiedlichen Datenbankverbindungen. In beiden Fällen gilt: Die Variable muss als Protected und als File Variable angelegt sein. Wer die env.php als reguläre String-Variable anlegt, riskiert Zeilenumbruch-Verlust und damit eine kaputte PHP-Datei auf dem Server.

7. Variables im .gitlab-ci.yml korrekt konsumieren

Wie Variables in der Pipeline konsumiert werden, ist genauso wichtig wie wie sie angelegt sind. File Variables werden als Dateipfad übergeben – das Script muss den Inhalt also über den Pfad lesen, nicht direkt als String verwenden. Normale Variablen stehen als Umgebungsvariablen zur Verfügung. Eine häufige Falle: Das direkte Einbetten von Variablenwerten in Shell-Zeichenketten ohne Anführungszeichen. $DEPLOY_PATH/current bricht, wenn DEPLOY_PATH Leerzeichen enthält. "$DEPLOY_PATH/current" ist korrekt.

Für den SSH-Setup in Before-Script-Blöcken gilt eine klare Reihenfolge: SSH-Agent starten, Key hinzufügen, Known Hosts schreiben, erst dann SSH- oder rsync-Befehle ausführen. Wer diese Reihenfolge falsch hat, bekommt entweder Authentifizierungsfehler oder – schlimmer – akzeptiert ungeprüfte Host-Keys. Variablen, die in before_script gesetzt werden, sind im script-Block desselben Jobs verfügbar. Variablen, die im script-Block gesetzt werden, sind in nachfolgenden Jobs nicht sichtbar, weil jeder Job in einer frischen Umgebung startet.

8. Vergleich: unsichere vs. korrekte Variable-Konfiguration

Der Unterschied zwischen einer improvisierten und einer sauberen Variable-Konfiguration ist in Magento-Projekten oft der Unterschied zwischen einer Pipeline, die zufällig funktioniert, und einer, die reproduzierbar und sicher ist.

Variable Unsicher / Falsch Korrekt Grund
SSH Private Key String Variable, kein Protected File Variable, Protected Kein Zeilenumbruch-Verlust; nur in protected Branches sichtbar
COMPOSER_AUTH auth.json im Repository Masked Variable, Scope prod/staging Nicht in Git-History; umgebungsgetrennt
env.php Im Repository eingecheckt File Variable oder Shared-Verzeichnis Crypt-Key und DB-Passwort niemals im Code
DEPLOY_HOST Scope * (alle Umgebungen) Scope production / staging Feature-Branches deployen nicht auf Production
DB_PASSWORD Klartext im .gitlab-ci.yml Masked + Protected Variable Nicht in Pipeline-Logs sichtbar

Die Tabelle zeigt: Jede Schwachstelle hat einen konkreten Gegenmaßnahmen-Typ in GitLab. Protected verhindert Branch-Leakage, Masked verhindert Log-Leakage, File Variable verhindert Formatierungsfehler bei mehrzeiligen Inhalten, und Scope verhindert Umgebungs-Vermischung. Alle vier Mechanismen zusammen ergeben eine Variable-Konfiguration, die auch unter Stress – eilige Hotfixes, neue Teammitglieder, unerfahrene Pipelines – sicher bleibt.

9. Typische Fehlerbilder und wie man sie erkennt

Das häufigste Fehlerbild bei CI/CD Variables ist der SSH-Key-Fehler: "Permission denied (publickey)". Ursache ist fast immer entweder ein Key ohne korrekte Zeilenumbrüche (String Variable statt File Variable), eine falsche Dateiberechtigung (700 statt 600 für den Key), ein fehlender Known-Hosts-Eintrag oder ein falscher Deployment-User. Die Diagnose: Im Job-Script ssh -v statt ssh verwenden – die verbose Ausgabe zeigt genau, welcher Authentifizierungsschritt scheitert.

Das zweite häufige Fehlerbild ist der Composer-Auth-Fehler: "Could not find a matching version". Ursache: Die COMPOSER_AUTH-Variable ist nicht gesetzt oder hat ein falsches JSON-Format. Diagnose: Im Build-Job composer config --list ausgeben und prüfen, ob die http-basic-Credentials für repo.magento.com vorhanden sind. Das dritte Fehlerbild ist das Scope-Mismatch: Ein Job hat keinen Zugriff auf eine Variable, die er braucht – weil sein Environment-Name nicht zum Scope der Variable passt. Die Prüfung: In den Job-Details in der GitLab-UI unter "Variables" nachschauen, welche Variablen für diesen spezifischen Job sichtbar waren. Das gibt sofort Aufschluss darüber, ob Scope und Environment-Name übereinstimmen.

# Debugging CI/CD Variable issues in a job
debug:variables:
  stage: build
  environment: staging
  script:
    # Print all variable names (NOT values — never print secret values)
    - env | grep -E '^(DEPLOY_|COMPOSER_|SSH_KNOWN|APP_)' | cut -d= -f1
    # Verify SSH key file exists and has correct permissions
    - ls -la "$SSH_PRIVATE_KEY"
    # Verify Composer auth is readable
    - composer config --list | grep -i "http-basic" || echo "No composer auth found"
    # Verify known hosts
    - ssh-keyscan "$DEPLOY_HOST" 2>/dev/null | ssh-keygen -lf - || echo "Host not reachable"
  when: manual
  allow_failure: true

10. Zusammenfassung

CI/CD Variables in GitLab richtig anzulegen ist keine Kleinigkeit am Rande der Pipeline-Konfiguration, sondern der Sicherheits- und Stabilitätsfundament jedes Magento-Deployments. SSH Private Keys als File Variable mit Protected-Flag – niemals als String Variable. Composer Auth als Masked Variable mit Environment Scope, nie im Repository. app/etc/env.php im Shared-Verzeichnis oder als File Variable, niemals eingecheckt. Jede Variable mit dem engstmöglichen Scope, jede kritische Credential als Protected.

Der größte Hebel ist die konsequente Anwendung des Scoping-Prinzips: Staging und Production bekommen nie dieselben Variables, weil der Environment Scope das systematisch verhindert. In Kombination mit protected Branches, die Production-Deployments auf freigegebene Pipelines beschränken, entsteht eine Sicherheitsarchitektur, die auch unter operativem Stress – eilige Fixes, Personalwechsel, neue Projekte – keine ungewollten Secrets-Abflüsse produziert.

CI/CD Variables in GitLab — Das Wichtigste auf einen Blick

SSH Keys

Als File Variable anlegen – Protected-Flag setzen. Known Hosts als separate Variable. Niemals StrictHostKeyChecking=no.

Composer Auth

Als Masked + Protected Variable mit Environment Scope. JSON-Format für Composer direkt übergeben. Nie in auth.json im Repository.

Environment Scope

Staging und Production bekommen eigene Variablen-Sätze. Scope * nur für nicht-sensitive Defaults wie RELEASE_RETENTION.

Diagnose

Variablennamen (nicht Werte) im Job ausgeben. Job-Details in GitLab-UI prüfen. SSH-Verbindung mit -v debuggen.

11. FAQ: CI/CD Variables in GitLab richtig anlegen

1Unterschied zwischen Protected und Masked?
Protected: nur in protected Branches/Tags sichtbar. Masked: in Job-Logs unkenntlich. Kritische Credentials brauchen beides.
2SSH Key als File Variable – warum?
GitLab schreibt den Inhalt in eine temporäre Datei – kein Zeilenumbruch-Verlust. Als String Variable wird der mehrzeilige Key oft beschädigt.
3COMPOSER_AUTH korrekt anlegen?
Als Masked Variable im JSON-Format: {"http-basic": {"repo.magento.com": {...}}}. Composer liest COMPOSER_AUTH automatisch aus der Umgebung.
4Was ist Environment Scope?
Beschränkt eine Variable auf Jobs mit passendem environment:-Namen. Staging und Production bekommen eigene Variablen-Sätze mit eigenem Scope.
5Darf env.php im Repository liegen?
Nein. Crypt Key, DB-Passwörter und Redis-Credentials gehören ins Shared-Verzeichnis oder als File Variable in GitLab.
6Variable im Job nicht verfügbar – warum?
In der GitLab-UI in den Job-Details unter Variables prüfen. Häufigste Ursache: Scope passt nicht zum environment-Namen des Jobs.
7Group-Level Variables sinnvoll?
Ja – für projektübergreifende Credentials wie Composer Auth oder SSH Keys. Alle Projekte der Gruppe erben die Variables automatisch.
8StrictHostKeyChecking=no – Problem?
Ja. SSH akzeptiert dann jeden Host-Key ohne Prüfung – Man-in-the-Middle-Risiko. SSH_KNOWN_HOSTS Variable mit geprüftem Fingerprint ist die sichere Alternative.
9Wie Secrets aus Logs fernhalten?
Masked Variables nutzen. Kein set -x in Jobs mit Secrets – Bash-Trace gibt expandierte Variablenwerte aus.
10Was darf auf Scope * stehen?
Nur nicht-sensitive Defaults: RELEASE_RETENTION, GIT_STRATEGY, Build-Cache-Pfade. Credentials und umgebungsspezifische Werte brauchen engere Scopes.