Composer kennt jeder PHP-Entwickler. Aber die wenigsten haben selbst schon ein Package gebaut und in einer privaten Registry veröffentlicht. Dabei ist das kein Hexenwerk: ein composer.json, eine GitLab-CI mit wenigen Zeilen und eine Integration im Ziel-Projekt.
1. Warum eigene Composer-Packages?
Wer in mehreren Projekten arbeitet, sammelt früher oder später Code, der überall gleich ist: eine Konfiguration, ein Helper, eine Schnittstellen-Bibliothek. Die klassische Lösung ist Copy-Paste – bis die Kopien auseinanderlaufen und Bugfixes nie zusammenfinden.
Ein Composer-Package löst das strukturell. Der gemeinsame Code lebt in einem eigenen Repository, bekommt eine Versionsnummer und wird als Abhängigkeit deklariert. Ein composer update im Ziel-Projekt holt die neue Version eines Packages. Dazu kommen die üblichen Vorteile: klare Versionsgrenzen über Semver, eine eigene Release-Historie, ein integrierter Autoloader und die Trennung zwischen „Bibliothek" und „Anwendung, die sie benutzt".
GitLab bietet dafür mit der Composer-Registry eine fertige Infrastruktur: Jedes Projekt bzw. jede Gruppe hat automatisch eine Registry-URL, und das Veröffentlichen läuft über die CI. Kein externer Dienst, kein Packagist, und kein VCS-Repo muss den Lesern Zugriff auf den ganzen Code einräumen.
2. Das Package erstellen
Ein Composer-Package besteht im Kern aus einer composer.json und dem Code, den sie beschreibt. Die Datei legt man nicht von Hand an – Composer bringt dafür eigene Befehle mit. Im Projektordner:
composer initcomposer init fragt interaktiv die wichtigsten Angaben ab (Name, Beschreibung, Lizenz, Autoload-Prefix) und erzeugt daraus eine gültige composer.json. Abhängigkeiten fügt man später mit composer require vendor/package hinzu.
Das Ergebnis sieht so aus (minimal um die wesentlichen Einträge ergänzt):
{
"name": "madikon/package",
"description": "A simple composer package",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Madikon\\Package\\": "src/"
}
},
"minimum-stability": "stable",
"require": {}
}Die entscheidenden Felder, die composer init abfragt bzw. die man ergänzt:
nameim Formatvendor/package– der Vendor-Name (hiermadikon) gruppiert deine Packages, der Paketname ist einmalig innerhalb des Vendors.type: librarymarkiert ein wiederverwendbares Package (der Standard für genau diesen Zweck).autoloadmit PSR-4 sagt Composer, in welchem Verzeichnis die Klassen mit welchem Namespace liegen.Madikon\Package\MyClassliegt dann physisch untersrc/MyClass.php.
3. Veröffentlichen über die GitLab-CI
GitLab bietet für jedes Projekt (und jede Gruppe) eine Package-Registry. Die Veröffentlichung passiert in der CI, ausgelöst durch das Taggen eines Releases.
Die .gitlab-ci.yml im Package-Repository kann wie folgt aussehen:
deploy:
stage: deploy
rules:
- if: '$CI_COMMIT_TAG'
script:
- apk add --update curl && rm -rf /var/cache/apk/*
- |
curl --fail-with-body \
--header "Job-Token: $CI_JOB_TOKEN" \
--data tag=$CI_COMMIT_TAG \
"${CI_API_V4_URL}/projects/$CI_PROJECT_ID/packages/composer"Der Ablauf im Detail: Ein Release wird getaggt. Entweder via git tag 1.0.0 && git push origin 1.0.0 oder bequem über die GitLab-Oberfläche: unter Repository → Tags auf „Neuen Tag erstellen" klicken und den Namen vergeben. Die Version im Format X.Y.Z ist später genau die Versionsnummer, unter der das Package in der Registry landet. Der Tag löst die Pipeline aus und der deploy-Job veröffentlicht das Package automatisch. Er ruft dafür die GitLab-API auf: POST /projects/<id>/packages/composer mit dem Tag als Payload (Details: Publish a Composer package). Die Authentifizierung erfolgt über den Header Job-Token: $CI_JOB_TOKEN – einen temporären Token, den GitLab jeder Pipeline mitgibt und der das Package dem Projekt zuordnet. Kein Personal Access Token im Repo, kein Secret-Setup.
4. Einbinden in ein anderes Projekt
Das Ziel-Projekt ist ein ganz normales Composer-PHP-Projekt. Zwei Dinge müssen konfiguriert werden: die Registry-URL als repository und das Package als require.
Die composer.json des Ziel-Projekts:
{
"name": "madikon/test",
"type": "project",
"repositories": [
{
"type": "composer",
"url": "https://gitlab.com/api/v4/group/madikons/-/packages/composer/packages.json"
}
],
"autoload": {
"psr-4": {
"Madikon\\Test\\": "src/"
}
},
"require": {
"madikon/package": "^1.0"
}
}Der repositories-Eintrag sagt Composer: „Such auch in dieser Registry, nicht nur auf Packagist."
Da die Registry privat ist, muss sich Composer allerdings ausweisen. Dazu muss Composer mitgeteilt werden, mit welchen Credentials die Authentifizierung erfolgen soll. Dies kann mit dem folgenden Befehl erledigt werden:
composer config http-basic.gitlab.com <NUTZERNAME> <TOKEN>Der Aufruf schreibt die Zugangsdaten in eine auth.json im Projektverzeichnis. Genau diese Datei ist der Grund, warum hier Disziplin nötig ist: Sie darf niemals ins Repository. Steht sie im Repo, liegen die Zugangsdaten im Klartext in der Git-Historie – und damit potenziell bei jedem, der Zugriff auf das Projekt hat.
In die .gitignore des Ziel-Projekts gehört daher zwingend:
auth.jsonAlternativ lässt sich der Zugang auch global hinterlegen – dann landet die auth.json im Home-Verzeichnis statt im Projektordner. composer config --global http-basic.gitlab.com <NUTZERNAME> <TOKEN> speichert sie zentral außerhalb des Projekts, sodass keine .gitignore-Zeile nötig ist und kein Token im Repo abgelegt wird.
Welche Token-Arten gibt es?
Für den Zugriff aus dem Ziel-Projekt gibt es zwei praktische Wege:
Personal Access Token – der eigene Token aus den GitLab-Einstellungen. Für lokale Entwicklung der notwendige Weg: Token einmal anlegen, dann den composer config - Befehl ausführen, fertig. Die auth.json liegt danach dauerhaft im Projektverzeichnis.
CI_JOB_TOKEN – der temporäre Token, den GitLab jeder Pipeline automatisch mitgibt. Keine Verwaltung, kein Ablaufdatum im eigentlichen Sinn, aber er existiert nur innerhalb des Pipeline-Laufs. Deshalb ist er in der CI der richtige Weg.
Beispiel für die Gitlab-CI Konfiguration im Zielprojekt:
composer-install:
image: composer:2
stage: build
cache:
key:
files:
- composer.lock
paths:
- vendor/
before_script:
- composer config http-basic.gitlab.com gitlab-ci-token ${CI_JOB_TOKEN}
script:
- composer install --no-interaction --prefer-dist --optimize-autoloader
after_script:
- rm -f auth.json
artifacts:
paths:
- vendor/
expire_in: 1 weekIm before_script erzeugt der composer config-Aufruf die auth.json mit dem CI_JOB_TOKEN, im after_script wird sie wieder gelöscht. Für die lokale Nutzung macht man dasselbe mit dem eigenen Token – der Unterschied ist nur, dass die Datei dann bleibt (und in der .gitignore stehen muss).
5. Fazit
Der Workflow ist überschaubar: Package definieren, Release taggen – das Veröffentlichen übernimmt die CI automatisch. Der Aufwand für die Einrichtung liegt bei etwa einer halben Stunde. Updates im Zielprojekt werden analog zu anderen Packages via composer update ... eingespielt.
Der größte Gewinn ist nicht mal die bequeme Registry. Es ist, dass der gemeinsame Code ab jetzt eine Versionsnummer hat, eine Release-Historie und eine klare Schnittstelle – und dass die Frage „Welche Version des Helpers nutzt eigentlich Projekt B?" eine Antwort hat, die man nachschlagen kann statt erraten.
Weiterführende Links:
Quellen:
- GitLab-Dokumentation: Composer packages in the package registry (Stand: September 2026)
- Composer-Dokumentation: composer config und auth.json-Schema
- Eigene Projektpraxis (Stand: September 2026)
Hinweis zur Erstellung: Textentwurf und Recherche entstanden zum Teil mit KI-Unterstützung, Redaktion und Fachinhalte von mir; das Beitragsbild ist KI-generiert. Fachliche Angaben wurden gegen die offizielle GitLab- und Composer-Dokumentation geprüft.