Private Composer-Repositories in Magento 2: Eigene Module sauber verteilen
AI generated
M2
di.xml
Magento 2 · Composer · Satis · Modul-Distribution
Private Composer-Repositories
für eigene Magento-Module betreiben

Wer eigene Magento-2-Module über mehrere Kundenprojekte hinweg pflegt, kommt an einem privaten Composer-Repository nicht vorbei. Statt Module manuell per FTP oder ZIP zu verteilen, macht ein privates Composer-Repository sie über composer require installierbar, versioniert über Git-Tags und mit vollständiger Abhängigkeitsauflösung gegen magento/framework. Dieser Artikel zeigt, wie Sie von der repositories-Konfiguration über eine eigene Satis-Instanz bis zur abgesicherten CI-Pipeline eine belastbare Verteilungsinfrastruktur aufbauen.

18 Min. Lesezeit repositories-Key · Satis · auth.json · CI Magento 2.4.8 · Composer 2 · PHP 8.4

1. Warum ein privates Composer-Repository für Agenturen unverzichtbar ist

Jede Agentur, die eigene Magento-2-Module über mehrere Kundenprojekte hinweg pflegt, kennt das Problem: ein Modul wird für Projekt A entwickelt, kurze Zeit später soll dieselbe Funktionalität in Projekt B oder C landen. Ohne strukturierte Verteilung endet das fast immer im manuellen Kopieren einer ZIP-Datei per FTP in den Ordner app/code, oft in einer Version, die längst nicht mehr dem aktuellen Entwicklungsstand entspricht. Ein privates Composer-Repository löst genau dieses Verteilungsproblem, indem es eigene Module genauso wie öffentliche Pakete von Packagist über composer require installierbar macht, samt korrekter Versionsangabe und automatischer Abhängigkeitsauflösung.

Der eigentliche Mehrwert eines privaten Composer-Repositorys zeigt sich erst im Zusammenspiel mit Composers Abhängigkeitsauflösung. Ein Modul, das intern magento/framework in einer bestimmten Version voraussetzt oder von einem zweiten eigenen Paket abhängt, lässt sich über composer.lock reproduzierbar in jeder Umgebung installieren, von der lokalen Entwicklungsmaschine über die CI-Pipeline bis zum Produktivserver. Genau diese Reproduzierbarkeit geht komplett verloren, sobald Module manuell kopiert werden: composer kennt die installierte Version dann nicht mehr, und ein composer update reißt im schlimmsten Fall den manuell eingespielten Code wieder heraus oder ignoriert ihn stillschweigend.

Ein Composer-Repository in privater Form schafft außerdem die Grundlage für sauberes Release-Management. Jede neue Modulversion bekommt eine eigene, über einen Git-Tag referenzierte Versionsnummer, wodurch jedes Kundenprojekt gezielt auf genau der Version bleiben kann, die getestet und freigegeben wurde, während ein anderes Projekt bereits auf eine neuere Minor-Version aktualisiert. Ohne dieses Repository müsste diese Versionsdisziplin von Hand über Ordnernamen oder Kommentare in einer README nachgebildet werden, was in der Praxis regelmäßig auseinanderdriftet.

2. Der repositories-Schlüssel in composer.json: vcs, composer und path

Composer sucht Pakete standardmäßig auf packagist.org. Damit ein eigenes, privates Modul überhaupt gefunden wird, muss es im repositories-Schlüssel der root composer.json eines Projekts eingetragen werden, entweder projektweit oder zentral über eine gemeinsame Basis-composer.json, die alle Kundenprojekte einbinden. Composer durchsucht dabei alle konfigurierten Repositories in der angegebenen Reihenfolge, bis es ein Paket mit passendem Namen und einer zur Versionsbeschränkung passenden Version findet.

Der Typ "vcs" verweist direkt auf ein Git-Repository, etwa eine private GitLab- oder GitHub-URL. Composer liest darüber unmittelbar aus den Tags und Branches des Repositorys, ganz ohne zusätzliche Paketmetadaten. Das ist der einfachste Einstieg in ein privates Composer-Repository, skaliert aber schlecht: für jedes einzelne Modul braucht das Projekt einen eigenen vcs-Eintrag, was bei einer Agentur mit einem Dutzend oder mehr eigenen Modulen schnell unübersichtlich wird.

Der Typ "composer" verweist stattdessen auf einen einzigen Endpunkt, hinter dem ein statischer oder dynamischer Paket-Feed liegt, etwa eine selbst gehostete Satis-Instanz, Private Packagist, ein Nexus-Repository oder ein vergleichbares Toolkit. Ein einziger Eintrag im repositories-Schlüssel genügt dann, unabhängig davon, wie viele private Module dahinter tatsächlich verwaltet werden. Der Typ "path" schließlich zeigt auf ein lokales Verzeichnis und wird primär für die lokale Entwicklung genutzt: Composer legt dabei, wo vom Betriebssystem unterstützt, einen Symlink statt einer echten Kopie in vendor/ an, sodass Änderungen am Modul sofort ohne erneuten composer update sichtbar sind.


{
  "repositories": [
    {
      "type": "composer",
      "url": "https://satis.mironsoft.de"
    },
    {
      "type": "vcs",
      "url": "https://git.mironsoft.de/mironsoft/module-seosuite.git"
    },
    {
      "type": "path",
      "url": "../local-modules/module-seosuite",
      "options": {
        "symlink": true
      }
    }
  ]
}

3. Eine eigene Satis-Instanz für das private Composer-Repository aufsetzen

Satis ist Composers eigenes, offizielles Werkzeug, um aus mehreren Git-Repositories einen einzigen statischen Paket-Feed zu erzeugen, und damit die naheliegende Wahl für eine selbst gehostete Lösung. Die Installation erfolgt über composer create-project composer/satis, die eigentliche Konfiguration liegt in einer satis.json, in der jedes einzubindende Modul-Repository mit seiner Git-URL sowie optionalen Versions- und Namensfiltern aufgeführt wird. Der Befehl satis build satis.json public/ liest diese Konfiguration, klont beziehungsweise aktualisiert jedes referenzierte Repository und schreibt eine packages.json samt aller Release-Archive in das Ausgabeverzeichnis.

Da Satis ausschließlich statische Dateien erzeugt, ist ein frisch gesetzter Git-Tag in einem Modul-Repository für Composer zunächst unsichtbar, bis Satis erneut gebaut wird. In der Praxis übernimmt das ein Cron-Job, der satis build in einem festen Intervall, etwa alle fünfzehn Minuten, erneut ausführt, oder ein Webhook, der direkt nach einem Git-Push die Neuerstellung anstößt. Das Ausgabeverzeichnis public/ wird anschließend per nginx oder Apache über HTTPS ausgeliefert, wobei ein Directory-Listing bewusst deaktiviert bleibt, damit niemand ohne gültige Paket-URL im Feed stöbern kann.

Der große Vorteil dieses statischen Ansatzes gegenüber einer dynamischen Lösung ist, dass zur Laufzeit kein PHP-Prozess und keine Datenbank nötig sind, um Composer-Anfragen zu beantworten, ein einfacher Webserver genügt vollständig. Der Nachteil ist die bereits angesprochene Verzögerung zwischen Tag und sichtbarer Verfügbarkeit im privaten Composer-Repository, die bei einem zu groß gewählten Cron-Intervall zu verwirrenden Situationen führt, in denen eine gerade veröffentlichte Version lokal, aber noch nicht in der CI-Pipeline auflösbar ist.


{
  "name": "mironsoft/private-satis",
  "homepage": "https://satis.mironsoft.de",
  "repositories": [
    { "type": "vcs", "url": "https://git.mironsoft.de/mironsoft/module-seosuite.git" },
    { "type": "vcs", "url": "https://git.mironsoft.de/mironsoft/module-blog.git" }
  ],
  "require": {
    "mironsoft/module-seosuite": "^1.0",
    "mironsoft/module-blog": "^2.0"
  },
  "require-all": false,
  "output-html": false,
  "archive": {
    "directory": "dist",
    "format": "zip"
  }
}

#!/usr/bin/env bash
# Cron entry rebuilding the static packages.json every 15 minutes
# */15 * * * * cd /var/www/satis && php bin/satis build satis.json public/ >> var/log/satis.log 2>&1

# Manual rebuild after a webhook fires on a new git push
php bin/satis build satis.json public/

# Resolve and install a module from the private Composer repository
bin/composer require mironsoft/module-seosuite:^1.2

# Update only a single package against the rebuilt feed
bin/composer update mironsoft/module-seosuite --with-dependencies

4. Zugriff auf das private Composer-Repository absichern: auth.json und COMPOSER_AUTH

Ein privates Composer-Repository ist per Definition nicht für den öffentlichen Zugriff gedacht, entsprechend braucht sowohl der Zugriff auf private Git-Repositories vom Typ vcs als auch auf einen Satis- oder Private-Packagist-Endpunkt in aller Regel eine Authentifizierung. Composer liest die dafür nötigen Zugangsdaten aus einer Datei namens auth.json, die entweder projektlokal neben der composer.json oder global im COMPOSER_HOME-Verzeichnis liegen kann. Unterstützt werden unter anderem http-basic für klassische Benutzername-Passwort-Kombinationen, wie sie eine einfache Satis-Instanz hinter einem Reverse-Proxy mit Basic-Auth verwendet, sowie bearer-Tokens, wie sie Private Packagist oder Nexus typischerweise ausgeben.

Die auth.json darf niemals in ein Git-Repository eingecheckt werden, sie gehört konsequent in die .gitignore jedes Projekts. Für Umgebungen, in denen keine Datei auf der Festplatte abgelegt werden soll, etwa in kurzlebigen CI-Containern oder beim Bauen eines Docker-Images, bietet Composer die Umgebungsvariable COMPOSER_AUTH an. Sie akzeptiert exakt dieselbe JSON-Struktur wie eine auth.json-Datei, wird aber als String über die Umgebung injiziert und landet dadurch nie als Datei im Image-Layer oder im Repository.

Im Mark-Shust-Docker-Setup lässt sich COMPOSER_AUTH bequem über die compose.yaml als Umgebungsvariable des PHP-Containers setzen, oder als Build-Argument an einen mehrstufigen Dockerfile-Build übergeben, ohne dass Zugangsdaten im fertigen Image sichtbar bleiben. Alternativ kann eine auth.json read-only in den Container gemountet werden, ohne dass sie Teil des Repositorys wird. In beiden Fällen greift bin/composer install im Container zuverlässig auf das private Composer-Repository zu, ohne dass Entwickler Zugangsdaten manuell in composer.json eintragen müssen.


{
  "http-basic": {
    "satis.mironsoft.de": {
      "username": "ci-deploy",
      "password": "REPLACE_WITH_SECRET"
    }
  },
  "bearer": {
    "repo.packagist.com": "REPLACE_WITH_TOKEN"
  }
}

5. Die composer.json eines Magento-Moduls für den Vertrieb vorbereiten

Damit ein eigenes Magento-Modul überhaupt über ein privates Composer-Repository installierbar ist, braucht es im eigenen Repository eine gültige composer.json. Der Typ magento2-module ist dabei entscheidend, denn er sagt dem Composer-Plugin von Magento, wie und wohin das Paket installiert werden muss, nämlich per Symlink oder Kopie nach app/code/Vendor/Modul, statt wie ein gewöhnliches Paket nach vendor/. Der Name folgt der Konvention vendor/modul-name in Kleinbuchstaben mit Bindestrich, muss inhaltlich aber dem PHP-Namespace Vendor\Modul entsprechen, den Magentos eigenes Modulsystem über registration.php und etc/module.xml erwartet.

Der autoload-Block mit psr-4 bildet den Namespace des Moduls auf sein Wurzelverzeichnis ab, damit Composer die Klassen des Moduls korrekt in den Autoloader einbindet. Ebenso wichtig ist ein sauber gesetzter require-Eintrag für magento/framework mit einer passenden Versionsbeschränkung, damit Composer bei der Installation automatisch prüft, ob die Zielumgebung überhaupt kompatibel ist, statt ein Modul in eine inkompatible Magento-Version zu installieren und den Fehler erst zur Laufzeit sichtbar zu machen.

Wichtig ist die Abgrenzung: composer.json regelt ausschließlich die Distribution und Installation des Pakets über das Composer-Repository, nicht die Registrierung des Moduls im Magento-eigenen Modulsystem. registration.php und etc/module.xml bleiben davon unberührt und müssen unabhängig davon korrekt gepflegt werden. Ein häufiger Fehler ist außerdem der veraltete extra.installer-name-Schlüssel aus frühen Magento-2-Versionen, der seit dem dedizierten magento2-module-Composer-Plugin nicht mehr benötigt wird und in neuen Modulen ersatzlos entfallen kann.


{
  "name": "mironsoft/module-seosuite",
  "description": "Mironsoft SeoSuite module for Magento 2",
  "type": "magento2-module",
  "license": "proprietary",
  "require": {
    "php": "~8.4.0",
    "magento/framework": "^103.0"
  },
  "autoload": {
    "psr-4": {
      "Mironsoft\\SeoSuite\\": ""
    },
    "files": [
      "registration.php"
    ]
  }
}

6. Versionierung per Git-Tag: wie Composer Versionsbeschränkungen auflöst

Composer löst Versionsbeschränkungen wie ^1.2, ~1.4 oder eine feste Version ausschließlich gegen tatsächlich vorhandene Git-Tags im Modul-Repository auf. Jede Freigabe einer neuen Modulversion braucht deshalb einen eigenen Tag im Format v1.2.0, wahlweise auch ohne führendes v, da Composer beide Schreibweisen intern normalisiert. Ohne einen solchen Tag sieht weder ein vcs-Eintrag noch eine Satis-Instanz überhaupt eine installierbare Version, das Modul wäre dann nur als instabile dev-main-Referenz nutzbar.

In der Praxis läuft der Ablauf so: der Tag wird nach einem abgeschlossenen und getesteten Feature lokal gesetzt und gepusht, was bei einer Satis-basierten Lösung entweder den nächsten Cron-Rebuild oder einen sofortigen Webhook-Rebuild auslöst. Erst danach taucht die neue Version im packages.json-Feed des privaten Composer-Repositorys auf und kann von einem consuming Projekt per composer update vendor/modul angefordert werden, sofern die dort hinterlegte Versionsbeschränkung sie zulässt.

An dieser Stelle geht es bewusst nicht um die Feinheiten korrekter SemVer-Nummerierung oder eine Breaking-Change-Politik, dazu gibt es einen eigenen, tiefergehenden Artikel zur Magento-Modul-Versionierung. Für die reine Distribution über ein privates Composer-Repository zählt vor allem eines: konsequente Tagging-Disziplin ist die Voraussetzung dafür, dass Versionsbeschränkungen überhaupt etwas bedeuten und Composer reproduzierbare Installationen liefern kann.

7. CI-Pipelines mit Zugriff auf das private Composer-Repository betreiben

Ein composer install läuft in praktisch jeder CI-Pipeline mehrfach, in der Test-, Build- und Deploy-Stufe. Damit dieser Schritt gegen ein privates Composer-Repository überhaupt funktioniert, braucht der Pipeline-Runner dieselben Zugangsdaten wie ein Entwickler lokal, üblicherweise als COMPOSER_AUTH hinterlegt in den geschützten CI-Variablen von GitLab CI oder GitHub Actions, niemals im Klartext im Repository. Zusätzlich muss der Runner selbst Netzwerkzugriff auf den Satis-Host oder Git-Server haben, was bei restriktiven Firewall- oder VPN-Regeln bedeutet, dass auch die IP-Bereiche der CI-Runner explizit freigegeben werden müssen.

Für die Laufzeit der Pipeline lohnt sich außerdem ein Composer-Cache-Verzeichnis, das zwischen Pipeline-Läufen erhalten bleibt, statt bei jedem Build sämtliche Modul-Archive erneut vom privaten Composer-Repository herunterzuladen. Ein gecachtes Verzeichnis kann mehrere Minuten Pipeline-Laufzeit einsparen, besonders bei größeren Projekten mit vielen eigenen Modulen. Bei locker gefassten Versionsbeschränkungen wie dev-main braucht dieser Cache jedoch eine bewusste Invalidierungsstrategie, damit nicht versehentlich ein veralteter Commit-Stand ausgeliefert wird.

Ein typisches Fehlerbild entsteht, wenn die statische packages.json einer Satis-Instanz noch nicht neu gebaut wurde: ein Entwickler pusht einen neuen Tag, composer update funktioniert lokal bereits, während dieselbe Pipeline kurz danach noch die alte Version auflöst, weil der Cron-Rebuild erst in einigen Minuten läuft. Genau dieses Szenario ist ein starkes Argument dafür, den Rebuild des Composer-Repositorys per Webhook statt nur per Cron-Intervall auszulösen.

8. Repository-Typen im Vergleich: vcs, composer, path und Packagist

Bevor ein privates Composer-Repository konkret aufgesetzt wird, lohnt sich ein direkter Vergleich der verfügbaren Repository-Typen, weil sie sich in Setup-Aufwand, typischem Einsatzzweck und Versionsunterstützung deutlich unterscheiden.

Repository-Typ Setup-Aufwand Einsatzzweck Versionsunterstützung
vcs Gering, nur URL eintragen Einzelnes Modul, schneller Einstieg, Ausnahmefälle Direkt über Git-Tags und Branches
composer (Satis / Private Packagist) Mittel bis hoch, eigene Instanz oder SaaS Viele eigene Module über mehrere Kundenprojekte hinweg Zentraler Feed, versioniert über alle Repositories
path Sehr gering Aktive lokale Entwicklung mit Symlink Keine echte Versionierung, immer aktueller Stand
Packagist (öffentlich) Keiner, bereits vorhanden Öffentliche Abhängigkeiten wie magento/framework Vollständige SemVer-Unterstützung

In der Praxis kombiniert eine Agentur meist mehrere dieser Typen: path für die aktive lokale Entwicklung, composer über eine Satis- oder Private-Packagist-Instanz für Staging und Produktion, und weiterhin ganz normal Packagist für alle öffentlichen Abhängigkeiten wie magento/framework selbst. Der Typ vcs bleibt sinnvoll für einzelne Ausnahmefälle oder den schnellen Einstieg, bevor sich eine Investition in eine vollwertige Satis-Instanz überhaupt lohnt.

9. Praktischer Workflow: von der lokalen path-Repository bis zum produktiven Release

In der aktiven Entwicklungsphase eines Moduls trägt ein Entwickler typischerweise eine path-Repository in seine lokale composer.json ein, die auf den lokalen Checkout des Modul-Repositorys zeigt. Das erlaubt sofortiges Testen jeder Codeänderung im Zielprojekt, ganz ohne den Umweg über Commit, Tag, Push und einen erneuten composer update gegen das private Composer-Repository.

Sobald ein Feature fertig und getestet ist, wird committet, ein passender Git-Tag gesetzt und gepusht. Die lokale path-Repository wird anschließend entfernt oder durch die reguläre composer-Repository-Konfiguration ersetzt, sodass Staging und Produktion die getaggte Version konsequent über das private Composer-Repository beziehen, nie über einen lokalen Dateisystempfad, der auf einem Produktivserver ohnehin nicht existiert.

Dieser Ablauf spiegelt genau die Realität einer Agentur mit mehreren Kundenprojekten wider: dieselbe Modulversion wird parallel von mehreren composer.json-Dateien unterschiedlicher Projekte angefordert, jede mit einer eigenen, bewusst gewählten Versionsbeschränkung. Das private Composer-Repository fungiert dabei als einzige, verlässliche Quelle, die alle Projekte gleichzeitig bedient, ohne dass irgendwo manuell eine ZIP-Datei kopiert werden muss.

Mironsoft

Composer-Infrastruktur, Satis-Betrieb und CI-Integration für Magento-2-Module

Eigene Magento-Module zuverlässig verteilen?

Wir richten euer privates Composer-Repository ein, von der Satis-Instanz über auth.json-Absicherung bis zur CI-Pipeline, die bei jedem Build zuverlässig auf eure eigenen Module zugreift.

Satis-Setup

satis.json, Cron- oder Webhook-Rebuild und HTTPS-Feed sauber aufsetzen

Sichere Distribution

auth.json und COMPOSER_AUTH ohne Zugangsdaten im Repository konfigurieren

CI-Integration

Composer-Zugriff und Caching in eure Pipeline zuverlässig einbauen

10. Zusammenfassung

Ein privates Composer-Repository ist die Infrastruktur-Grundlage für jede Agentur, die eigene Magento-2-Module über mehr als ein Kundenprojekt hinweg pflegt. Ob über einen einfachen vcs-Eintrag, eine selbst gehostete Satis-Instanz oder einen verwalteten Dienst wie Private Packagist: entscheidend ist, dass Composer eigene Module genauso zuverlässig auflösen kann wie öffentliche Pakete von Packagist, samt korrekter Versionsauflösung und Abhängigkeitsprüfung gegen magento/framework.

Der Betrieb eines privaten Composer-Repositorys bringt zusätzliche Verantwortung mit, von der Absicherung über auth.json und COMPOSER_AUTH bis zur Sicherstellung, dass CI-Pipelines denselben Zugriff und dieselbe Aktualität sehen wie ein Entwickler lokal. Wer diese Bausteine sauber verdrahtet, gewinnt eine Verteilungsinfrastruktur, die mit der Anzahl eigener Module und Kundenprojekte mitwächst, statt bei jedem neuen Projekt erneut manuell Code hin und her zu kopieren.

Privates Composer-Repository: das Wichtigste auf einen Blick

Warum ein privates Repository

Versionierte, reproduzierbare Verteilung eigener Module statt manueller FTP-Kopien über mehrere Kundenprojekte hinweg.

Repository-Typen

vcs für den schnellen Einstieg, composer über Satis oder Private Packagist für den produktiven Betrieb, path für lokale Entwicklung.

Sicherheit

auth.json und COMPOSER_AUTH schützen Zugangsdaten, niemals im Repository, immer als Umgebungsvariable oder read-only Mount.

CI-Betrieb

Gleicher Zugriff wie lokal, gecachte Composer-Downloads und Webhook-Rebuilds statt langer Cron-Intervalle.

11. FAQ: Privates Composer-Repository für Magento 2

1Was ist ein privates Composer-Repository und wozu braucht eine Agentur es?
Es macht eigene, nicht öffentliche Magento-Module über composer require installierbar, statt sie manuell per FTP oder ZIP zu kopieren, inklusive Versionierung und Abhängigkeitsauflösung.
2Was unterscheidet type vcs von type composer?
vcs verweist direkt auf ein einzelnes Git-Repository, composer verweist auf einen zentralen Feed wie Satis oder Private Packagist für beliebig viele Module.
3Wie richte ich eine eigene Satis-Instanz ein?
Über composer create-project composer/satis, eine satis.json mit den Modul-Repositories und satis build satis.json public/, ausgeliefert per HTTPS.
4Wie oft muss Satis neu gebaut werden?
Üblich sind Cron-Intervalle von etwa 15 Minuten oder ein Webhook direkt nach jedem Git-Push.
5Wie funktioniert Authentifizierung über auth.json?
Über http-basic oder bearer-Tokens, projektlokal oder global im COMPOSER_HOME, niemals in ein Git-Repository eingecheckt.
6Was macht COMPOSER_AUTH?
Injiziert dieselbe JSON-Struktur wie auth.json als Umgebungsvariable, ideal für CI-Container und Docker-Images.
7Welche Angaben braucht die composer.json eines Moduls?
Type magento2-module, Name vendor/modul-name, psr-4-Autoload und ein require-Eintrag für magento/framework.
8Warum braucht Composer Git-Tags für Versionen?
Composer löst Versionsbeschränkungen nur gegen vorhandene Tags auf. Ohne Tag ist ein Modul nur als instabile dev-main-Referenz nutzbar.
9Wie greift eine CI-Pipeline auf das Repository zu?
Über COMPOSER_AUTH in geschützten Pipeline-Variablen, plus Netzwerkzugriff des Runners auf Satis-Host oder Git-Server.
10Ist type path für Produktivumgebungen geeignet?
Nein, path zeigt auf einen lokalen Pfad. Staging und Produktion sollten über composer- oder vcs-Repositories installieren.