CI/CD
.yml
GitLab · Composer · Magento · Build-Pipeline
Composer Install in GitLab CI
sauber, reproduzierbar und performant ausführen

Ein composer install in CI, der manchmal andere Pakete installiert als erwartet, der bei jedem Run 200 Pakete neu herunterlädt, oder der auf Production andere Dependencies produziert als auf Staging – das sind lösbare Probleme. Dieser Artikel erklärt, wie composer install in GitLab CI reproduzierbar, schnell und korrekt ausgeführt wird.

10 Min. Lesezeit composer.lock · --no-dev · Cache · Artefakte Composer 2.x · GitLab 16.x · PHP 8.4

1. Das Grundprinzip: composer install vs. composer update

Der wichtigste Unterschied zwischen composer install und composer update in einer CI-Pipeline ist der Unterschied zwischen Reproduzierbarkeit und Zufall. composer install installiert exakt die Versionen, die in der composer.lock-Datei festgeschrieben sind. composer update löst neue Versionen auf und aktualisiert die Lock-Datei. In einer CI-Pipeline hat composer update nichts verloren: Es kann neue, ungetestete Paketversionen einbringen, die sich zwischen zwei Build-Runs unterscheiden. Für Magento-Projekte, deren Abhängigkeiten komplex und oft von Patches abhängig sind, ist das ein ernsthaftes Risiko.

Die composer.lock muss eingecheckt sein – das ist keine Frage der Präferenz, sondern eine Voraussetzung für reproduzierbare Builds. Ohne Lock-Datei in Git kann keine CI-Pipeline garantieren, dass heute und morgen dieselben Pakete installiert werden. Magento-Projekte mit kommerziellen Erweiterungen sind besonders anfällig: Eine aktualisierte Drittanbieter-Extension kann Breaking Changes enthalten, die erst im Production-Deploy auffallen. Die Lock-Datei ist das Protokoll, das festhält, was getestet und freigegeben wurde.

2. composer.lock als Reproduzierbarkeits-Basis

Die composer.lock-Datei enthält die exakten Versionen und Checksums aller Abhängigkeiten – direkte und transitive. Wenn composer install diese Datei findet, installiert es exakt diese Versionen und verifiziert die Checksums. Schlägt die Checksum-Verifikation fehl, bricht der Build ab – das ist das gewollte Verhalten. Eine Checksummen-Abweichung ist ein Signal, dass ein Paket manipuliert wurde oder die Lock-Datei nicht konsistent mit dem Repository ist.

In GitLab-Pipelines gilt: Die Lock-Datei muss immer auf dem aktuellen Stand des Branches sein. Eine häufige Fehlerquelle ist der Merge-Konflikt in composer.lock, der durch einen einfachen git checkout -- composer.lock aufgelöst wird – was dazu führt, dass die Lock-Datei nicht mehr zu composer.json passt. Der korrekte Weg bei Merge-Konflikten in composer.lock ist, die Datei vollständig neu zu generieren mit composer update --lock, das die Lock-Datei aktualisiert ohne Paketversionen zu ändern. Wer das nicht tut, baut mit einer inkonsistenten Lock-Datei, die Composer im schlimmsten Fall mit einem Fehler abbricht.

# Reproducible Composer install job for Magento in GitLab CI
build:composer:
  stage: build
  image: php:8.4-cli
  variables:
    COMPOSER_CACHE_DIR: ".cache/composer"
    # COMPOSER_AUTH injected from GitLab CI/CD Variable (masked, protected)
  cache:
    # Cache key based on lock file hash — regenerates when dependencies change
    key:
      files:
        - composer.lock
      prefix: "composer-php84"
    paths:
      - .cache/composer/
    policy: pull-push
  before_script:
    - apt-get update -qq && apt-get install -y -qq git unzip libzip-dev libpng-dev libicu-dev
    - docker-php-ext-install zip intl bcmath gd sockets
    - curl -sS https://getcomposer.org/installer | php -- --quiet
    - mv composer.phar /usr/local/bin/composer
  script:
    # Install exactly what composer.lock specifies — no updates
    - composer install
        --no-dev
        --prefer-dist
        --no-interaction
        --no-ansi
        --optimize-autoloader
    # Verify lock file integrity — fail if composer.lock and composer.json are out of sync
    - composer validate --no-check-publish
  artifacts:
    paths:
      - vendor/
    expire_in: 2 hours
    when: on_success

3. --no-dev: Was wegfällt und warum das wichtig ist

Das Flag --no-dev bei composer install schließt alle Pakete aus, die unter require-dev in composer.json deklariert sind. Das umfasst typischerweise PHPUnit, PHPStan, PHP CS Fixer, Mockery und weitere Test- und Entwicklungs-Tools. Diese Pakete sind in einem Production-Build nicht notwendig – und ihr Ausschluss hat zwei positive Effekte: Der Vendor-Ordner wird kleiner (relevanter Faktor bei rsync-Deployments), und das Autoloader-Mapping enthält keine Development-Only-Klassen, die in Production nie geladen werden sollten.

Ein häufiger Fehler: --no-dev im Build-Job, aber kein separater Test-Job, der mit Dev-Dependencies baut. Das führt dazu, dass Tests entweder in Production-Builds laufen (mit zu vielen Paketen) oder gar nicht laufen. Der korrekte Ansatz: Ein separater Test-Stage-Job, der composer install ohne --no-dev ausführt, Unit-Tests und statische Analyse durchführt und dann verwirft. Der Build-Job für Deployment-Artefakte läuft separat mit --no-dev. Diese Trennung macht die Pipeline klarer und schneller, weil Test-Jobs und Build-Jobs unabhängig voneinander laufen können.

4. Die wichtigsten Composer-Flags für CI

Neben --no-dev gibt es weitere Flags, die in CI-Pipelines Standard sein sollten. --prefer-dist lädt Pakete als fertige Archive herunter statt als Git-Clones, was deutlich schneller ist und besser mit Composer-Cache zusammenspielt. --no-interaction verhindert, dass Composer im Job auf Benutzereingaben wartet, die in CI nie kommen würden – ein hängender Job ist das Ergebnis, wenn dieses Flag fehlt. --no-ansi deaktiviert Farb-Escape-Codes in der Ausgabe, die in einigen CI-Log-Systemen als Zeichenmüll erscheinen.

--optimize-autoloader (oder -o) generiert einen optimierten Classmap-Autoloader statt des Standard-PSR-4-Autoloaders. Das verbessert die Performance in Production messbar, weil PHP keine Verzeichnispfade traversieren muss. Für Magento-Projekte mit hunderten von Modulen ist dieser Unterschied spürbar. Alternativ bietet Composer 2 das Flag --classmap-authoritative, das noch einen Schritt weiter geht und den Autoloader weigerlich macht, PSR-4-Fallbacks zu verwenden – nur bei vollständigem Classmap sinnvoll, für Magento-Projekte mit dynamisch generiertem Code aber problematisch.

5. Composer-Cache in GitLab richtig konfigurieren

Der Composer-Cache in GitLab speichert heruntergeladene Paket-Archive zwischen Pipeline-Runs. Ohne Cache lädt jeder Build alle Pakete neu herunter – bei einem Magento-Projekt mit 200+ Abhängigkeiten eine erhebliche Zeitverschwendung und ein Rate-Limit-Risiko beim Magento Marketplace. Der Cache-Key sollte die composer.lock-Datei als Basis haben: Wenn sich die Lock-Datei ändert, wird ein neuer Cache-Eintrag erzeugt; wenn nicht, wird der bestehende Cache wiederverwendet. Das verhindert Cache-Poisoning (zu alter Cache für neue Dependencies) und unnötige Cache-Misses (neuer Cache obwohl nichts geändert hat).

Das policy-Feld im Cache-Block steuert, ob ein Job den Cache lesen, schreiben oder beides tun soll. Build-Jobs sollten pull-push verwenden, um den Cache nach dem Build zu aktualisieren. Test-Jobs, die denselben Cache verwenden aber keine neuen Pakete herunterladen, sollten pull verwenden, damit sie nicht versehentlich einen Cache mit einem anderen Stand überschreiben. Wer mehrere PHP-Versionen in seiner Pipeline testet, muss den Cache-Key um die PHP-Version erweitern, weil manche Composer-Pakete PHP-versionsspezifische Downloads haben.

6. vendor/ als Build-Artefakt: was deployt wird

Die vendor/-Verzeichnis als GitLab-Artefakt zu übergeben bedeutet, dass nachfolgende Jobs (Deploy, Verify) exakt dasselbe Vendor-Verzeichnis verwenden wie der Build-Job. Das schließt die Möglichkeit aus, dass ein Deploy-Job eine andere PHP-Umgebung hat, die Composer anders interpretiert. Für Magento-Projekte ist das besonders relevant, weil generated/ und DI-Code von der spezifischen Vendor-Version abhängen. Artefakte haben eine Ablaufzeit (expire_in); typisch sind 1-2 Stunden für Deployment-Artefakte, weil sie nach dem Deploy nicht mehr benötigt werden.

Was nicht ins Artefakt gehört: .cache/composer/ (das ist der Composer-Cache, nicht das Vendor-Verzeichnis), Entwicklungs-Konfigurationsdateien und temporäre Build-Dateien. Die Entscheidung, ob vendor/ überhaupt als Artefakt behandelt wird oder direkt per rsync auf den Server deployt wird, hängt vom Deployment-Modell ab. Bei Multi-Server-Deployments empfiehlt sich das Artefakt-Modell, weil alle Server exakt dieselben Pakete erhalten. Bei Single-Server-Deployments kann der rsync-Ansatz direkter sein, solange der Build auf demselben Runner-System läuft.

7. PHP-Image und Extensions im Build-Job

Das PHP-Image im Build-Job muss zu der PHP-Version passen, die auf dem Production-Server läuft. Ein Mismatch führt zu Extensions oder Abhängigkeiten, die in der Build-Umgebung vorhanden sind, auf dem Server aber fehlen – und umgekehrt. Für Magento 2.4.8 auf PHP 8.4 ist php:8.4-cli das richtige Base-Image. Extensions wie intl, bcmath, zip, gd und sockets werden von Magento benötigt und müssen im before_script-Block installiert werden, bevor Composer läuft, weil Composer die PHP-Extension-Voraussetzungen aus composer.json prüft und fehlende Extensions als Fehler behandelt.

Der Vorteil eines Docker-Images gegenüber einem Shell-Runner ist die Isolation: Jeder Build startet in einer frischen, definierten Umgebung, ohne Rückstände von vorherigen Builds. Das ist die Grundlage für Reproduzierbarkeit. Wer einen Shell-Runner verwendet, muss sicherstellen, dass die System-PHP-Version und alle Extensions auf dem Runner-System konsistent mit dem Production-Server sind – eine Anforderung, die bei System-Updates schnell verletzt wird. Das Docker-Image ist daher die bevorzugte Wahl für Build-Jobs.

8. Vergleich: naiver vs. reproduzierbarer Composer-Build

Der Unterschied zwischen einem schnell hingeschriebenen und einem sauber konzipierten Composer-Build in GitLab CI ist auf den ersten Blick klein, in der Praxis aber erheblich.

Aspekt Naiv / Fragil Reproduzierbar Auswirkung
Befehl composer update composer install Keine ungetesteten Paket-Updates im Build
Dev-Pakete Mit Dev-Dependencies deployen --no-dev Kleinerer Vendor-Ordner, kein Test-Code in Production
Cache-Key Branch-Name oder keine Cache Hash von composer.lock Cache-Miss nur bei echten Dependency-Änderungen
PHP-Image latest oder falsche Version php:8.4-cli (Production-Version) Build-Umgebung = Production-Umgebung
Autoloader Standard PSR-4 --optimize-autoloader Messbar schnellere Autoloader-Performance in Production

Jede Zeile der Tabelle repräsentiert eine Entscheidung, die im normalen Betrieb unsichtbar bleibt – aber in dem Moment sichtbar wird, wenn ein Release auf Production anders verhält als erwartet: falsche Paketversion, zu großer Vendor-Ordner, langsame Startzeit, Build der auf Staging funktioniert aber auf Production scheitert. Reproduzierbare Builds eliminieren diese Klasse von Problemen systematisch.

9. Typische Fehlerbilder im Composer-Build

Das häufigste Fehlerbild ist Your lock file does not contain a compatible set of packages. Das bedeutet, dass composer.lock und composer.json nicht mehr konsistent sind – z.B. nach einem Merge, bei dem der Merge-Konflikt in composer.lock falsch aufgelöst wurde. Lösung: Lokal composer update --lock ausführen, die regenerierte Lock-Datei committen. Das zweite häufige Fehlerbild ist ein fehlgeschlagener Download mit einem HTTP 401 oder 403 – die COMPOSER_AUTH-Variable ist nicht gesetzt oder hat falsche Credentials. Diagnose: Scope der Variable prüfen, JSON-Format validieren.

Ein drittes, schwerer zu diagnostizierendes Fehlerbild ist der stille Unterschied zwischen Build-Umgebung und Production-Server: Auf dem Build-Runner ist eine PHP-Extension installiert, die auf dem Server fehlt. Composer installiert Pakete, die diese Extension voraussetzen, ohne Fehler – aber auf dem Server schlägt der Autoloader fehl. Vermeidung: PHP-Extensions im Build-Image explizit auf Production-Parität halten und composer check-platform-reqs im Build-Job ausführen, das die Extension-Voraussetzungen aller installierten Pakete gegen die aktuelle PHP-Umgebung prüft.

# Platform requirements check — catches extension mismatches early
build:validate:
  stage: build
  image: php:8.4-cli
  needs: ["build:composer"]
  script:
    # Validate that composer.json and composer.lock are in sync
    - composer validate --no-check-publish --strict
    # Check all installed packages have their PHP extension requirements met
    - composer check-platform-reqs
    # Verify no security advisories for installed packages
    - composer audit --no-dev
  artifacts:
    when: always
    reports:
      # Output composer audit as a JSON artifact for GitLab Security Dashboard
      junit: vendor/composer/installed.json
    expire_in: 1 week

10. Zusammenfassung

Composer install in GitLab CI reproduzierbar auszuführen setzt drei Dinge voraus: composer install statt update, damit die Lock-Datei als Basis gilt; --no-dev, damit keine Entwicklungs-Pakete in Production-Artefakte gelangen; und ein Cache-Key basierend auf composer.lock, damit Pakete nicht bei jedem Build neu heruntergeladen werden. Ergänzend: Das PHP-Image muss der Production-PHP-Version entsprechen, und --optimize-autoloader muss gesetzt sein, damit der generierte Autoloader in Production performant ist.

Die composer.lock-Datei gehört ins Repository – das ist keine optionale Konvention, sondern die technische Grundlage für Reproduzierbarkeit. Ein Magento-Build, der bei zwei Runs unterschiedliche Pakete installiert, ist kein verlässlicher Build. Die Lock-Datei dokumentiert, was getestet und freigegeben wurde. Diese Dokumentation ist das, was zwischen "es hat beim letzten Release funktioniert" und "wir wissen, was deployt wird" unterscheidet.

Composer Install in GitLab CI — Das Wichtigste auf einen Blick

Reproduzierbarkeit

composer install (nicht update) + composer.lock im Repository. Identische Pakete bei jedem Build-Run garantiert.

Production-Artefakt

--no-dev + --optimize-autoloader. Keine Test-Tools in Production, optimierter Autoloader für bessere Performance.

Cache-Strategie

Cache-Key basierend auf composer.lock. Neu nur bei echten Dependency-Änderungen. Rate-Limits des Marketplace vermeiden.

PHP-Umgebung

Build-PHP-Version = Production-PHP-Version. composer check-platform-reqs im Job ausführen.

11. FAQ: Composer Install in GitLab CI

1composer install statt update – warum?
install nutzt composer.lock als Basis – exakt die getesteten Versionen. update löst neue auf – riskiert ungetestete Änderungen in Production.
2composer.lock ins Repository?
Ja – Pflicht. Ohne Lock-Datei im Repository keine reproduzierbaren Builds. Die Datei dokumentiert was getestet wurde.
3Was bringt --no-dev?
Schließt PHPUnit, PHPStan und andere Dev-Tools aus dem Artefakt aus. Kleinerer Vendor-Ordner, kein Test-Code in Production.
4Composer-Cache in GitLab richtig?
Cache-Key: Hash von composer.lock. Pfad: .cache/composer/. Policy pull-push für Build-Jobs, pull für Read-Only-Jobs.
5Lock file not compatible – Ursache?
composer.lock und composer.json inkonsistent. Lösung: composer update --lock lokal ausführen und regenerierte Lock-Datei committen.
6Welches PHP-Image?
Dieselbe Version wie Production – für Magento 2.4.8: php:8.4-cli. Versions-Mismatch führt zu Extension-Fehlern auf dem Server.
7Was tut check-platform-reqs?
Prüft PHP-Extension-Voraussetzungen aller installierten Pakete. Verhindert Builds die auf dem Runner funktionieren, auf dem Server aber scheitern.
8vendor/ als GitLab-Artefakt?
Ja – nachfolgende Jobs nutzen exakt denselben Vendor-Stand. expire_in auf 1-2h setzen, da nach dem Deploy nicht mehr benötigt.
9--optimize-autoloader vs. --classmap-authoritative?
--optimize-autoloader mit PSR-4-Fallback – für Magento geeignet. --classmap-authoritative ohne Fallback – für Magento mit generiertem Code problematisch.
10Tests mit Dev-Dependencies separat?
Ja – separater Test-Job ohne --no-dev, parallel zum Build-Job. Build erzeugt Deployment-Artefakt, Tests nutzen Dev-Dependencies.