GitLab CI fuer generische PHP-Microservices: Pipeline ohne Magento-Bezug
AI generated
CI/CD
.yml
GitLab · CI/CD · PHP-Microservices
GitLab CI fuer generische PHP-Microservices
Eine schlanke Pipeline ohne Magento-Ballast

Nicht jedes PHP-Projekt ist ein Monolith wie Magento. Fuer eigenstaendige Microservices mit Symfony oder Laravel reicht eine deutlich schlankere .gitlab-ci.yml, die trotzdem dieselben Grundprinzipien aus Build, Test und Deployment sauber abbildet.

17 Min. Lesezeit Symfony Laravel PHPUnit PHPStan

1. Warum eine Microservice-Pipeline anders aussieht als eine Magento-Pipeline

Eine Magento-Pipeline muss mit Besonderheiten wie Reindexierung, statischem Content-Deployment, Zero-Downtime-Wechseln zwischen Release-Verzeichnissen und einer komplexen Modul-Struktur umgehen. Ein eigenstaendiger PHP-Microservice, etwa ein Symfony-basierter Rechnungsdienst oder eine Laravel-API fuer Bestellstatus-Abfragen, hat davon nichts: Er besteht aus einem einzigen Composer-Projekt, einer ueberschaubaren Zahl an Abhaengigkeiten und meist einem klaren, containerisierten Deployment-Ziel.

Diese Reduktion ist ein Vorteil, kein Mangel. Eine generische Pipeline fuer einen Microservice kann sich auf vier klare Schritte konzentrieren: Abhaengigkeiten installieren, automatisierte Tests ausfuehren, statische Code-Analyse durchfuehren und ein deploybares Artefakt, meist ein Docker-Image, bauen und ausrollen. Wer versucht, Magento-Pipeline-Patterns eins zu eins auf ein solches Projekt zu uebertragen, baut unnoetige Komplexitaet ein, die weder gebraucht noch gewartet werden will.

2. Grundgeruest: Stage-Aufbau fuer eine generische PHP-Pipeline

Der Grundaufbau folgt einem einfachen linearen Muster: build installiert Abhaengigkeiten und stellt sie als Artefakt fuer nachfolgende Stages bereit, test fuehrt PHPUnit und statische Analyse parallel aus, und deploy baut und verteilt das finale Artefakt. Anders als bei Magento entfaellt eine eigene Stage fuer Static-Content-Deployment oder Reindexierung vollstaendig, weil es diese Konzepte in einem generischen PHP-Service schlicht nicht gibt.

Wichtig ist der stages:-Block am Anfang der Datei, der die Reihenfolge global festlegt. Jobs innerhalb derselben Stage laufen parallel, Stages selbst nacheinander, es sei denn, needs: definiert explizite Abhaengigkeiten quer durch die Stages, was bei kleineren Microservices oft gar nicht noetig ist, weil die Gesamtlaufzeit ohnehin gering bleibt.


stages:
  - build
  - test
  - deploy

variables:
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"

default:
  image: php:8.3-cli
  before_script:
    - apt-get update -qq && apt-get install -y -qq unzip git
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

3. Composer install und Caching als Basis fuer alle Folge-Jobs

Der build-Job installiert die Abhaengigkeiten einmal und stellt vendor/ als Artefakt fuer alle nachfolgenden Jobs bereit. Das vermeidet, dass jeder Test- und Analyse-Job composer install einzeln ausfuehrt, was bei mehreren parallelen Jobs unnoetig Zeit kostet und die Runner unnoetig belastet. Kombiniert mit einem CI-Cache fuer den Composer-Download-Cache beschleunigt sich auch der erste composer install spuerbar, da Paket-Zips nicht bei jedem Lauf erneut aus dem Netz geladen werden muessen.

Fuer produktionsnahe Builds ist composer install --no-dev --optimize-autoloader die richtige Wahl im finalen Deployment-Artefakt, waehrend Test- und Analyse-Jobs die Dev-Abhaengigkeiten wie PHPUnit oder PHPStan benoetigen. Ein sauberer Ansatz ist deshalb, im build-Job einmal mit Dev-Abhaengigkeiten zu installieren und Test-Jobs dieses Artefakt weiterzuverwenden, waehrend ein separater, spaeterer Schritt im Deploy-Kontext die schlanke Produktionsvariante erzeugt.


build:
  stage: build
  script:
    - composer install --no-interaction --prefer-dist
  cache:
    key: composer-cache
    paths:
      - $COMPOSER_CACHE_DIR
  artifacts:
    paths:
      - vendor/
    expire_in: 1 hour

4. PHPUnit mit Coverage-Auswertung im MR-Widget

GitLab liest Testabdeckung direkt aus der Job-Ausgabe aus, wenn eine coverage:-Regex im Job hinterlegt ist, und zeigt das Ergebnis als Prozentzahl im Pipeline-Widget an. Fuer detailliertere Auswertung, etwa Zeilen-genaue Markierung im Diff eines Merge Requests, wird zusaetzlich ein Cobertura-XML-Report als artifacts: reports: coverage_report benoetigt, den PHPUnit ueber --coverage-cobertura erzeugt.

Wichtig ist, dass Xdebug oder PCOV im verwendeten PHP-Image aktiv ist, da PHPUnit ohne Coverage-Treiber keine Abdeckungsdaten erzeugen kann und den Report-Schritt stillschweigend uebersprint. Fuer schnellere Testlaeufe empfiehlt sich PCOV gegenueber Xdebug, da es ausschliesslich fuer die Coverage-Erfassung entwickelt wurde und dadurch deutlich weniger Overhead im Vergleich zum vollen Xdebug-Debugger verursacht.


phpunit:
  stage: test
  needs: ["build"]
  script:
    - vendor/bin/phpunit --coverage-text --coverage-cobertura=coverage.xml
  coverage: '/^\s*Lines:\s*\d+.\d+\%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

5. Statische Code-Analyse mit PHPStan und PHP-CS-Fixer

Statische Analyse laeuft in einer eigenstaendigen Stage parallel zu PHPUnit, da beide Werkzeuge unabhaengig voneinander arbeiten und sich nicht gegenseitig blockieren muessen. PHPStan prueft Typkorrektheit und potenzielle Laufzeitfehler bereits vor der Ausfuehrung, waehrend PHP-CS-Fixer im --dry-run-Modus sicherstellt, dass der Code-Stil dem Projektstandard entspricht, ohne die Dateien tatsaechlich zu veraendern.

Beide Tools sollten die Pipeline bei Verstoessen aktiv fehlschlagen lassen, anders als etwa optionale Security-Scans, weil Stilverstoesse und Typfehler in einem gut gepflegten Microservice-Projekt kein akzeptabler Zustand fuer den main-Branch sind. Ein exit code ungleich null von PHPStan oder PHP-CS-Fixer --dry-run reicht dafuer bereits aus, ohne dass zusaetzliche allow_failure:-Konfiguration noetig waere.


phpstan:
  stage: test
  needs: ["build"]
  script:
    - vendor/bin/phpstan analyse src --level=8 --no-progress

php-cs-fixer:
  stage: test
  needs: ["build"]
  script:
    - vendor/bin/php-cs-fixer fix --dry-run --diff

6. Symfony-spezifische Schritte: Doctrine-Migrationen und Environment

Bei einem Symfony-Service kommen zwei zusaetzliche Pruefungen hinzu, die es bei einem generischen PHP-Projekt ohne Framework nicht gibt: doctrine:schema:validate stellt sicher, dass die Entity-Mappings zum tatsaechlichen Datenbankschema passen, und doctrine:migrations:migrate --dry-run in der test-Stage prueft, dass ausstehende Migrationen fehlerfrei anwendbar sind, ohne sie tatsaechlich auf einer echten Datenbank auszufuehren.

Da Symfony strikt zwischen APP_ENV=test, dev und prod unterscheidet, muss die CI-Umgebungsvariable APP_ENV explizit auf test gesetzt werden, sonst greift die Pipeline versehentlich auf eine Produktionskonfiguration zu, die in CI gar nicht existiert. Eine eigene .env.test.local, die nur in der Pipeline verwendet wird, haelt echte Zugangsdaten sauber von der CI-Konfiguration getrennt.


symfony-doctrine-check:
  stage: test
  needs: ["build"]
  variables:
    APP_ENV: test
  services:
    - postgres:16
  script:
    - php bin/console doctrine:schema:validate --skip-sync
    - php bin/console doctrine:migrations:migrate --dry-run --no-interaction

7. Laravel-spezifische Schritte: artisan test und Config-Cache

Bei Laravel uebernimmt artisan test die Rolle von PHPUnit als komfortabler Wrapper mit lesbarer Ausgabe und zusaetzlichen Laravel-spezifischen Assertions. Vor dem Testlauf muss die .env-Datei aus .env.testing kopiert und ein APP_KEY generiert werden, da Laravel ohne gueltigen Verschluesselungsschluessel bereits beim Bootstrapping mit einer Exception abbricht.

Zusaetzlich lohnt sich ein artisan config:cache und artisan route:cache Testlauf innerhalb der Pipeline, selbst wenn dieser Cache im Testkontext nicht dauerhaft genutzt wird. Er deckt fruehzeitig Fehler auf, die erst beim Cachen der Konfiguration sichtbar werden, etwa nicht aufloesbare Umgebungsvariablen in Config-Dateien, die im normalen unkgecachten Betrieb stillschweigend als null durchgereicht wuerden.


laravel-test:
  stage: test
  needs: ["build"]
  script:
    - cp .env.testing .env
    - php artisan key:generate
    - php artisan config:cache
    - php artisan test --parallel

8. Deployment als Docker-Image-Build und -Push

Der uebliche Deployment-Weg fuer einen containerisierten Microservice ist der Build eines Docker-Images innerhalb der Pipeline mit anschliessendem Push in die GitLab Container Registry, gefolgt von einem Rollout auf der Zielplattform, etwa via kubectl oder einem einfachen SSH-Restart auf einem Docker-Compose-Host. Das produktionsreife Image sollte dabei ein Multi-Stage-Dockerfile nutzen, das Composer-Dev-Abhaengigkeiten und Test-Tools gar nicht erst in das finale Image uebernimmt.

Anders als bei einer Magento-Instanz mit geteiltem Dateisystem und mehreren parallel laufenden PHP-FPM-Prozessen ist das Deployment eines Microservices meist ein einzelner, austauschbarer Container. Ein fehlgeschlagenes Deployment laesst sich deshalb haeufig einfach durch Zuruecksetzen auf das vorherige Image-Tag beheben, ohne die komplexeren Zero-Downtime-Mechanismen, die bei monolithischen Magento-Deployments noetig sind.


build-image:
  stage: deploy
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

9. Fazit: schlank bleiben statt Magento-Muster zu kopieren

Ein generischer PHP-Microservice profitiert am meisten von einer Pipeline, die genau die Schritte enthaelt, die das Projekt tatsaechlich braucht, und nicht von einer verkleinerten Kopie einer Magento-Pipeline mit uebernommenen, aber ungenutzten Konzepten. Build, Test, statische Analyse und ein einfacher Image-Build reichen fuer die meisten Symfony- oder Laravel-Services vollstaendig aus.

Framework-spezifische Ergaenzungen wie Doctrine-Migrationspruefungen bei Symfony oder artisan test bei Laravel sollten gezielt und minimal eingebaut werden, statt vorsorglich jede denkbare Pruefung zu integrieren. Eine kurze, gut lesbare .gitlab-ci.yml ist bei einem kleinen Microservice fast immer wertvoller als eine vollstaendige, aber unuebersichtliche Pipeline mit ungenutzten Stages.

Aspekt Magento-Pipeline Generische PHP-Microservice-Pipeline
Typische Stages build, static-content, test, deploy, reindex build, test, deploy
Deployment-Ziel Geteiltes Dateisystem, Zero-Downtime-Releases Einzelnes, austauschbares Docker-Image
Framework-Spezifika Modul-Kompilierung, DI-Compile, Static Content Doctrine-Migrationen oder artisan-Kommandos
Typische Laufzeit 10-30 Minuten 2-6 Minuten

Mironsoft

CI/CD-Pipelines, Zero-Downtime-Deployments und Release-Automatisierung

Deployments, die ohne Ausfallzeit und ohne Nervenkitzel laufen?

Wir prüfen bestehende GitLab-Pipelines auf fragile Deployment-Schritte und fehlende Absicherung und bauen daraus einen Release-Prozess mit Zero-Downtime-Deployments, automatisierten Checks und einem Rollback, dem ihr im Ernstfall vertrauen könnt.

Pipeline-Review

Bestehende .gitlab-ci.yml auf Fragilität, fehlende Stages und Sicherheitslücken prüfen.

Zero-Downtime-Deployment

Symlink-Releases, Health-Checks und Rollback-Strategien für Magento-Shops aufbauen.

CI/CD-Automatisierung

Tests, Security-Scans und Deployments zu einer zuverlässigen Pipeline verbinden.

10. Zusammenfassung

GitLab CI fuer PHP-Microservices: Das Wichtigste auf einen Blick

Vier Grundstufen

build, test, static analysis und deploy reichen fuer die meisten eigenstaendigen PHP-Services.

Artefakt-Wiederverwendung

vendor/ einmal im build-Job installieren und ueber artifacts: an alle Folge-Jobs weitergeben.

Framework-Extras gezielt

Doctrine-Checks bei Symfony, artisan test bei Laravel, nicht mehr als tatsaechlich gebraucht.

Container statt Zero-Downtime-Tricks

Ein austauschbarer Docker-Container ersetzt komplexe Release-Verzeichnis-Mechanismen.

11. FAQ: GitLab CI fuer PHP-Microservices: Das Wichtigste auf einen Blick

1Warum ist eine Microservice-Pipeline einfacher als eine Magento-Pipeline?
Weil ein Microservice keine geteilte Dateisystem-Struktur, keine Static-Content-Kompilierung und keine komplexe Modul-Landschaft hat. Build, Test und ein einzelner Deployment-Schritt reichen meist aus.
2Wie vermeide ich, dass jeder Job composer install einzeln ausfuehrt?
Der build-Job installiert die Abhaengigkeiten einmal und stellt vendor/ als artifacts: fuer alle Folge-Jobs bereit, sodass diese composer install nicht wiederholen muessen.
3Wie zeige ich die Testabdeckung im Merge-Request-Widget an?
Ueber ein coverage:-Regex im PHPUnit-Job fuer die Prozentanzeige und einen Cobertura-XML-Report unter artifacts: reports: coverage_report fuer zeilengenaue Markierung im Diff.
4Muss PHPStan die Pipeline bei Fehlern blockieren?
Ja, anders als optionale Scans sollte statische Analyse mit einem Exit-Code ungleich null die Pipeline aktiv fehlschlagen lassen, da Typfehler kein akzeptabler Zustand fuer den main-Branch sind.
5Was ist bei Symfony-Pipelines zusaetzlich zu pruefen?
doctrine:schema:validate stellt sicher, dass Entity-Mappings zum Datenbankschema passen, und doctrine:migrations:migrate --dry-run prueft ausstehende Migrationen ohne sie real anzuwenden.
6Was ist bei Laravel-Pipelines zusaetzlich zu pruefen?
artisan test als PHPUnit-Wrapper, ein generierter APP_KEY vor dem Testlauf sowie ein probeweiser artisan config:cache Lauf, um Fehler beim Konfigurations-Caching fruehzeitig zu finden.
7Warum PCOV statt Xdebug fuer Coverage in CI verwenden?
PCOV ist ausschliesslich fuer Coverage-Erfassung entwickelt und deutlich schneller als der volle Xdebug-Debugger, der zusaetzlichen Overhead fuer Step-Debugging mitbringt, den CI-Jobs nicht benoetigen.
8Wie wird das Deployment eines Microservices technisch umgesetzt?
Ueblicherweise durch Bauen eines Docker-Images in der Pipeline, Push in die GitLab Container Registry und anschliessenden Rollout ueber kubectl oder einen einfachen Restart auf dem Zielsystem.
9Wie unterscheidet sich ein fehlgeschlagenes Deployment bei einem Microservice von Magento?
Ein Microservice-Container laesst sich meist einfach durch Zuruecksetzen auf das vorherige Image-Tag reparieren, waehrend Magento komplexere Zero-Downtime-Mechanismen mit mehreren Release-Verzeichnissen benoetigt.
10Lohnt sich eine Multi-Stage-Dockerfile fuer PHP-Microservices?
Ja, dadurch bleiben Composer-Dev-Abhaengigkeiten und Test-Tools ausserhalb des finalen Produktions-Images, was die Image-Groesse reduziert und die Angriffsflaeche in Produktion verkleinert.