CI/CD
.yml
GitLab · Composer · Magento · Private Repositories
Composer Auth, Private Repositories und Mirrors
in GitLab sicher und reproduzierbar verwalten

Wer Composer-Credentials im Repository ablegt, riskiert Kompromittierung bei jedem Offboarding. Wer sie gar nicht verwaltet, kämpft mit fehlerhaften Builds. Dieser Artikel zeigt, wie auth.json, private Repository-Tokens und Packagist-Mirror-Konfigurationen sicher als GitLab Variables hinterlegt und in Pipelines korrekt konsumiert werden.

12 Min. Lesezeit auth.json · COMPOSER_AUTH · Private Packagist · Mirror Composer 2.x · GitLab 16.x · Magento 2.4.x

1. Das Problem mit Composer-Credentials in CI

Composer-Credentials – also Zugangsdaten für den Magento Marketplace, kommerzielle Erweiterungen und private Paketquellen – werden in vielen Projekten entweder im Repository als auth.json eingecheckt oder auf dem Build-Server manuell hinterlegt. Beides ist problematisch: Eingecheckte Credentials erscheinen in der Git-History und sind bei jedem Clone des Repositories zugänglich – auch für Entwickler, die längst das Team verlassen haben. Manuell hinterlegte Credentials auf Build-Servern sind unsichtbar, werden selten rotiert und hängen an der Lebensdauer des Servers.

GitLab CI/CD Variables lösen dieses Problem strukturell: Die Credentials liegen ausschließlich in GitLab, nicht im Repository und nicht auf dem Server. Sie können pro Umgebung gescopedt, per Protected-Flag auf authorized Branches beschränkt und jederzeit rotiert werden, ohne den Code oder die Server-Konfiguration zu berühren. Wer diesen Weg konsequent geht, hat einen reproduzierbaren Build-Prozess, bei dem jeder Entwickler dieselbe Pipeline ausführen kann, ohne lokale Credentials kennen zu müssen – und bei dem Offboarding keine manuelle Credentials-Rotation erfordert.

2. auth.json – Format, Inhalte und Grenzen

Die auth.json-Datei ist Composers primäre Methode, Authentifizierungs-Credentials zu speichern. Sie unterstützt drei Typen: http-basic für Username/Password-Authentifizierung (wie beim Magento Marketplace), bearer für Token-basierte Authentifizierung (wie bei GitLab Personal Access Tokens oder GitHub Tokens) und gitlab-token als spezieller Alias für GitLab-Token-Auth. Die Datei hat eine klare JSON-Struktur mit Host-Namen als Keys – Composer findet automatisch die passenden Credentials für eine gegebene Paketquelle.

Die Grenzen der auth.json: Sie ist eine lokale Datei, die pro Entwickler-Rechner oder CI-Runner hinterlegt werden muss. In CI-Umgebungen bedeutet das, dass die Datei entweder vorab auf dem Runner hinterlegt ist (schlechte Praxis, weil nicht versionierbar) oder in jedem Job-Script aus einer Variable erzeugt wird. Composer kennt dafür aber einen eleganteren Weg: Die Umgebungsvariable COMPOSER_AUTH enthält denselben JSON-Inhalt wie die Datei – Composer liest beide mit identischer Priorität. Das ist der empfohlene Weg für GitLab-Pipelines, weil die Variable direkt aus GitLab übergeben wird, ohne Datei-Schreibschritte im Script.

# auth.json format — used as COMPOSER_AUTH variable value in GitLab
# Set this entire JSON as the value of the COMPOSER_AUTH CI/CD Variable
{
  "http-basic": {
    "repo.magento.com": {
      "username": "public-key-from-marketplace",
      "password": "private-key-from-marketplace"
    },
    "hyva-themes.com": {
      "username": "token",
      "password": "your-hyva-license-key"
    }
  },
  "bearer": {
    "gitlab.example.com": "glpat-your-personal-access-token"
  }
}

3. COMPOSER_AUTH als GitLab Variable anlegen

Die Variable wird in GitLab unter Settings → CI/CD → Variables angelegt. Als Wert kommt der vollständige JSON-Inhalt der auth.json rein – ohne Dateiname, nur der JSON-String. Wichtige Konfiguration: Das Masked-Flag setzen, damit der JSON-Inhalt in Job-Logs nicht im Klartext erscheint. Das Protected-Flag setzen, wenn der Value nur in Production-Pipelines benötigt wird. Der Environment Scope sollte so eng wie möglich sein: Wenn Staging andere Composer-Credentials braucht als Production, sind zwei separate Variablen mit demselben Namen aber unterschiedlichem Scope die saubere Lösung.

Ein häufiger Fehler beim Anlegen: Das JSON enthält einen Zeilenumbruch-Fehler oder eine falsche Quotierung, was dazu führt, dass Composer die Variable ignoriert oder mit einem JSON-Parse-Fehler abbricht. Vor dem Anlegen der Variable empfiehlt es sich, den JSON-Inhalt lokal mit jq . auth.json zu validieren. Ein weiterer Fallstrick: Bei sehr langen Composer-Auth-Strings (viele Pakete, viele Credentials) kann die Masked-Funktion scheitern, weil GitLab eine Längenbeschränkung für Masked Variables hat. In diesem Fall muss auf das Protected-Flag allein vertraut werden, oder die Credentials werden auf mehrere Variables aufgeteilt.

4. Magento Marketplace: Public Key und Access Key

Der Magento Marketplace nutzt http-basic-Authentifizierung mit zwei unterschiedlichen Schlüsseln: Der Public Key entspricht dem Username, der Access Key dem Passwort. Beide werden im Marketplace-Account unter "Access Keys" generiert. Der Public Key kann vergleichsweise frei geteilt werden – er identifiziert den Account, enthält aber kein Geheimnis. Der Access Key ist das eigentliche Secret und muss als Masked und Protected Variable hinterlegt werden.

Für Produktionspipelines empfiehlt sich, dedizierte Marketplace-Keys für CI anzulegen, getrennt von den persönlichen Entwickler-Keys. So kann der CI-Key jederzeit rotiert oder gesperrt werden, ohne den Entwicklungs-Workflow zu unterbrechen. Ein weiterer Vorteil: Die Marketplace-API begrenzt Requests pro Key. Bei parallelen Builds auf mehreren Branches kann es zu Rate-Limit-Fehlern kommen, wenn alle Builds denselben persönlichen Key verwenden. Dedizierte CI-Keys haben höhere Limits und sind klarer dem automatisierten Build-Prozess zugeordnet.

5. Private Repositories: GitLab, GitHub und Satis

Private Composer-Pakete kommen in Magento-Projekten häufig von drei Quellen: einem eigenen GitLab-Repository (Custom-Modul), einem kommerziellen Anbieter mit privatem GitHub-Repository oder einem selbst gehosteten Satis-Server. Für alle drei Quellen wird die Authentifizierung über die COMPOSER_AUTH-Variable konfiguriert. GitLab-Repositories verwenden entweder einen Personal Access Token mit read_api-Scope oder einen Deploy Token, der ausschließlich Lesezugriff auf das Repository hat. Deploy Tokens sind die sicherere Wahl, weil sie nicht an einen Benutzer-Account gebunden sind und beim Offboarding nicht ungültig werden.

Für Satis-Server, die intern Composer-Pakete hosten, gelten dieselben Prinzipien: Basic-Auth mit dediziertem CI-Benutzer, Token in der COMPOSER_AUTH-Variable, kein Hardcoding im composer.json. Wichtig: Die Repository-URLs in composer.json dürfen keine eingebetteten Credentials enthalten (also kein https://user:password@satis.intern/). Die Credentials kommen ausschließlich aus der Variable. Das ermöglicht es, composer.json problemlos einzuchecken, ohne Credentials zu exponieren.

# Build job — consuming COMPOSER_AUTH from GitLab CI/CD Variable
build:composer:
  stage: build
  image: php:8.4-cli
  variables:
    # COMPOSER_CACHE_DIR enables GitLab cache for vendor packages
    COMPOSER_CACHE_DIR: ".cache/composer"
    # COMPOSER_AUTH is injected automatically from GitLab Variable
    # No need to write auth.json manually
  cache:
    key: "composer-$CI_COMMIT_REF_SLUG"
    paths:
      - .cache/composer/
    policy: pull-push
  before_script:
    - apt-get update -qq && apt-get install -y -qq git unzip
    - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    - php composer-setup.php --quiet
  script:
    # Composer automatically reads COMPOSER_AUTH environment variable
    - php composer.phar install --no-dev --prefer-dist --no-interaction --no-ansi
    - php composer.phar dump-autoload --optimize --no-dev
    # Verify no auth.json was accidentally written
    - test ! -f auth.json || { echo "ERROR: auth.json must not exist"; exit 1; }
  artifacts:
    paths:
      - vendor/
    expire_in: 2 hours

6. Composer Mirror und Private Packagist konfigurieren

Composer-Mirror und Private Packagist werden in Projekten eingesetzt, um Abhängigkeiten von der öffentlichen Packagist-Infrastruktur zu entkoppeln, Download-Zeiten zu reduzieren und Pakete intern zu cachen. Die Konfiguration eines Mirrors erfolgt über composer.json im repositories-Block oder über composer config-Befehle im Build-Script. Private Packagist spezifisch erfordert einen Token, der über die COMPOSER_AUTH-Variable übergeben wird – im selben JSON-Format wie bei anderen Bearer-Token-Quellen.

Wichtig bei Mirror-Konfigurationen: Der Mirror muss im Build-Job erreichbar sein. Wenn der GitLab-Runner in einem internen Netz läuft und der Mirror intern gehostet wird, ist das kein Problem. Wenn der Runner in der Cloud läuft und der Mirror intern ist, braucht es VPN oder einen externen Mirror. Die Konfiguration des Mirrors im composer.json selbst ist unproblematisch – sie enthält keine Credentials. Die Authentifizierung am Mirror kommt aus der Variable. Dieser Trennung zwischen Konfiguration (im Repository) und Credentials (in GitLab) konsequent zu folgen ist das zentrale Prinzip einer sicheren Composer-Setup.

7. Composer Auth in der Pipeline korrekt konsumieren

In der Pipeline gilt: Composer liest COMPOSER_AUTH automatisch – kein explizites Übergeben als Argument nötig. Das funktioniert, weil Composer standardmäßig die Umgebungsvariable COMPOSER_AUTH prüft und als Auth-Konfiguration behandelt, bevor es die lokale auth.json liest. Im .gitlab-ci.yml muss die Variable nur im richtigen Scope sichtbar sein; der Build-Job liest sie ohne weiteres Zutun. Ein häufiger Debugging-Schritt: Im Job composer config --list ausführen (nur in Debug-Jobs, nie in Production) – die Ausgabe zeigt, ob die http-basic-Credentials für repo.magento.com oder andere Quellen korrekt gesetzt sind.

Caching der vendor/-Verzeichnisse in Kombination mit korrekter Composer-Auth ergibt die beste Build-Performance. Der Cache-Key sollte composer.lock als Input einschließen, damit bei einer Änderung der Lock-Datei automatisch ein neuer Cache-Eintrag erzeugt wird. Wer den Vendor-Ordner cacht, muss aber sicherstellen, dass die gecachten Packages zum aktuellen Lock-File passen – ein Mismatch führt zu schwer debugbaren Fehler, weil Composer den Cache blind verwendet. Das Flag --no-cache in einen Test-Job einzubauen, der periodisch einen sauberen Build ohne Cache durchführt, ist eine gute Praxis.

8. Vergleich: unsicherer vs. sicherer Composer-Setup

Der Unterschied zwischen einem improvisierten und einem sicheren Composer-Setup in GitLab ist messbar – in Audit-Risiko, Rotationsaufwand und Build-Reproduzierbarkeit.

Aspekt Unsicher Korrekt Auswirkung
Credential-Ort auth.json im Repository COMPOSER_AUTH Variable Kein Credential-Abfluss über Git-History
Key-Typ Persönlicher Entwickler-Key Dedizierter CI-Key Rotierbar ohne Entwickler-Workflow-Unterbrechung
Scope Variable Scope * (alle Branches) Scope production / staging Feature-Branches sehen keine Production-Keys
Mirror URL https://user:pw@mirror/ in composer.json URL in composer.json, Auth in Variable composer.json sicher eincheckbar
Rotation Datei-Update auf jedem Server Variable in GitLab aktualisieren Rotation ohne Server-Zugriff möglich

Die konsequente Trennung von Konfiguration (im Repository) und Credentials (in GitLab Variables) ist das Grundprinzip, das alle Zeilen der Tabelle verbindet. Wer es einmal sauber aufgebaut hat, zahlt beim Onboarding neuer Entwickler, beim Offboarding alter Teammitglieder und beim Rotieren von Credentials jeweils deutlich weniger operativen Aufwand als Teams, die Credentials verteilt auf Servern und Repositories verwalten.

9. Typische Fehlerbilder bei Composer Auth in CI

Das häufigste Fehlerbild ist Could not authenticate against repo.magento.com. Ursachen: Die COMPOSER_AUTH-Variable ist nicht gesetzt (Scope-Problem), das JSON-Format ist ungültig (fehlende Anführungszeichen, Komma-Fehler), oder der Access Key ist abgelaufen. Diagnose: Im Build-Job echo $COMPOSER_AUTH | php -r "echo json_last_error() ? 'invalid JSON' : 'valid JSON';" ausführen, um das Format zu prüfen. Dann im Marketplace-Account prüfen, ob der Access Key noch aktiv ist.

Das zweite Fehlerbild ist Package not found bei privaten Repositories. Ursache: Der Token hat nicht die nötigen Scopes, oder die Repository-URL in composer.json stimmt nicht mit dem Credential-Host in der Variable überein. Diagnose: Den Token in der GitLab-Variable mit einem minimalen Read-Scope für das spezifische Repository anlegen und im Build-Job mit composer why-not prüfen, ob die Paketquelle überhaupt gefunden wird. Das dritte Fehlerbild ist ein Rate-Limit-Fehler vom Magento Marketplace – erkennbar an HTTP 429-Responses im Job-Log. Lösung: Composer-Cache in GitLab korrekt konfigurieren, damit dieselben Pakete nicht bei jedem Build neu heruntergeladen werden.

# Validate COMPOSER_AUTH and test connectivity in a debug job
debug:composer-auth:
  stage: build
  image: php:8.4-cli
  environment: staging
  script:
    # Validate JSON format of COMPOSER_AUTH without printing the value
    - |
      php -r "
        \$auth = getenv('COMPOSER_AUTH');
        if (!\$auth) { echo 'ERROR: COMPOSER_AUTH not set'; exit(1); }
        json_decode(\$auth);
        if (json_last_error() !== JSON_ERROR_NONE) {
          echo 'ERROR: Invalid JSON in COMPOSER_AUTH: ' . json_last_error_msg();
          exit(1);
        }
        echo 'COMPOSER_AUTH: valid JSON, length=' . strlen(\$auth) . PHP_EOL;
      "
    # Test connectivity to Magento Marketplace (no download, just HEAD request)
    - curl -sf -o /dev/null -w "HTTP %{http_code}" https://repo.magento.com/ || true
  when: manual
  allow_failure: true

10. Zusammenfassung

Composer Auth, private Repositories und Mirror-Konfigurationen in GitLab sicher zu verwalten ist das Fundament eines reproduzierbaren Magento-Builds in CI. COMPOSER_AUTH als Masked Variable mit dem JSON-Inhalt der auth.json – kein auth.json im Repository, kein manuelles Credential-Management auf Servern. Dedizierte CI-Keys statt persönlicher Entwickler-Keys für den Magento Marketplace. Environment Scope für die Variable, damit Feature-Branches keine Production-Credentials sehen. Composer-Cache in GitLab, damit Pakete nicht bei jedem Build neu heruntergeladen werden.

Das Ziel ist ein Build-Prozess, bei dem jeder Entwickler dieselbe Pipeline ausführen kann, ohne lokale Credentials zu kennen – und bei dem das Rotieren eines Marketplace-Keys oder eines Repository-Tokens eine Änderung in GitLab ist, nicht ein Koordinationsprojekt über drei Server und sechs Entwickler-Rechner. Diese Entkopplung ist es, die Composer Auth in GitLab vom pragmatischen Workaround zum dauerhaften Standard macht.

Composer Auth in GitLab — Das Wichtigste auf einen Blick

Credential-Ablage

COMPOSER_AUTH als Masked Variable – nie auth.json im Repository. JSON-Format vorab mit jq validieren.

Key-Management

Dedizierte CI-Keys für Magento Marketplace, getrennt von persönlichen Entwickler-Keys. Deploy Tokens für GitLab-interne private Repos.

Scope & Rotation

Environment Scope eng definieren. Rotation erfolgt nur in GitLab – keine Server-Updates, kein Code-Change.

Mirror & Cache

Mirror-URLs in composer.json, Credentials in Variable. Composer-Cache in GitLab konfigurieren, Rate-Limits vermeiden.

11. FAQ: Composer Auth in GitLab sicher verwalten

1COMPOSER_AUTH in GitLab anlegen?
Als CI/CD Variable, Wert = JSON-Inhalt der auth.json. Masked-Flag setzen. JSON vorher mit jq validieren.
2Darf auth.json im Repository liegen?
Nein – steht in der Git-History und ist bei jedem Clone zugänglich. Credentials nur als GitLab Variable.
3Dedizierte CI-Keys – warum?
Rotierbar ohne Entwickler-Workflow-Unterbrechung, höhere Rate-Limits und klar dem CI-Prozess zugeordnet.
4Private GitLab-Repos in Composer?
Deploy Token mit read_repository-Scope als bearer-Entry in COMPOSER_AUTH. URL in composer.json, Auth in Variable.
5Mirror vs. Private Packagist?
Mirror: cached Packagist-Pakete lokal. Private Packagist: gehosteter privater Paket-Server. Beide reduzieren externe Abhängigkeit.
6Masked Variable schlägt fehl?
GitLab hat eine Längenbeschränkung. Bei zu vielen Credentials: Auf Protected statt Masked setzen oder Credentials aufteilen.
7COMPOSER_AUTH korrekt prüfen?
php -r "json_decode(getenv('COMPOSER_AUTH')); echo json_last_error();" – gibt 0 aus wenn JSON valide. Wert nie direkt ausgeben.
8Repository-URLs in composer.json einchecken?
Ja – URLs sind nicht geheim. Credentials kommen aus COMPOSER_AUTH. Keine eingebetteten Credentials in URLs.
9Rate-Limit-Fehler vom Marketplace vermeiden?
Composer-Cache in GitLab aktivieren, Cache-Key auf composer.lock basieren, --prefer-dist nutzen.
10Richtiger Scope für COMPOSER_AUTH?
Wenn Staging und Production gleiche Credentials nutzen: ein Scope reicht. Wenn nicht: separate Variablen mit demselben Namen und unterschiedlichem Scope.