Die Verwaltung von Softwareversionen ist eine der grundlegenden Herausforderungen in der modernen Softwareentwicklung. Ohne ein durchdachtes Versionierungskonzept droht das gefĂĽrchtete „Dependency Hell“ – ein Zustand, in dem Abhängigkeiten zwischen Softwarekomponenten zu unvorhersehbaren Fehlern und Inkompatibilitäten fĂĽhren. Dieser Beitrag zeigt, wie wir die semantische Versionierung in unseren GitLab CI/CD-Pipelines automatisiert haben, mit dem Ziel die Protokollierung, Nachverfolgbarkeit und Konsistenz ĂĽber alle Deployment-Artefakte hinweg zu gewährleisten – von OCI-Images bis hin zu Programm-Bibliotheken.
Das Problem „Dependency Hell“
In der Welt des Software-Managements existiert ein gefĂĽrchteter Ort namens „Dependency Hell“. Je größer ein System wächst und je mehr Pakete integriert werden, desto wahrscheinlicher ist es, dass man sich eines Tages an diesem Abgrund der Verzweiflung wiederfindet.
Ein anschauliches Beispiel liefert André Monteiro in seinem Blog: Eine NestJS-Anwendung, die wochenlang problemlos lief, stürzte plötzlich nach einem neuen Build ab – nur weil eine transitive Abhängigkeit (das ws-Paket) die Unterstützung für Node 6 in Version 7 eingestellt hatte, ohne dass die übergeordneten Pakete ihre Versionsnummern entsprechend angepasst hatten.
Solche Szenarien verdeutlichen, warum semantische Versionierung nicht nur eine Best Practice, sondern eine Notwendigkeit fĂĽr professionelle Softwareentwicklung ist.
Was ist Semantische Versionierung ?
Semantische Versionierung (Semantic Versioning) ist ein Konzept von Tom Preston-Werner, Mitgründer von GitHub und Erfinder von Gravatar, für die Versionsnummernvergabe von Programmen und Softwarekomponenten. Die vollständige Spezifikation ist unter semver.org verfügbar. Eine Versionsnummer besteht aus drei Teilen: MAJOR.MINOR.PATCH (z. B. 1.10.24). Zusätzlich verwenden wir optional einen Build‑Indikator als Pre‑Release‑Suffix -bN (z. B. 1.10.24-b1). Hinweis: Die SemVer‑Spezifikation notiert Build‑Metadaten mit einem + (z. B. 1.10.24+0041). Wir weichen hier bewusst leicht ab und verwenden -bN; die Bedeutung (interner Buildzähler) bleibt gleich.
1.2.3-b4
│ │ │ └── Build (bN) – interner Buildzähler
│ │ └── Patch
│ └─── Minor
└──── Major
Die Regeln der semantischen Versionierung
| Versionstyp | Beschreibung | Beispiel |
|---|---|---|
| PATCH | Fehlerkorrekturen, abwärtskompatibel | 1.0.0 → 1.0.1 |
| MINOR | Neue Funktionen, abwärtskompatibel | 1.0.0 → 1.1.0 |
| MAJOR | Inkompatible API-Änderungen, Breaking Changes | 1.0.0 → 2.0.0 |
| BUILD | Entwicklungsfortschritt, interne Builds | 1.0.0 → 1.0.0-b1 |
Hauptversionsnummer (Major Release): Eine Änderung der MAJOR-Versionszahl bedeutet, dass es Änderungen an der Art und Weise gibt, wie das Programm, Skript oder die Softwarekomponente zu verwenden ist – zum Beispiel Änderungen der Eingabeparameter oder Breaking Changes in der API.
Nebenversionsnummer (Minor Release): Eine Änderung der MINOR-Versionszahl bedeutet eine Erweiterung der Funktionen ohne Beeinträchtigung der Kompatibilität zu früheren Versionen. Es gibt neue Funktionen, aber die alten Funktionen funktionieren wie bisher.
Revisionsnummer (Patch Release): Eine Änderung der PATCH-Versionszahl bedeutet eine Fehlerkorrektur, die voll kompatibel zu früheren Versionen ist. Hier hat sich nur intern etwas geändert.
Buildnummer: Die Buildnummer kennzeichnet den Fortschritt der Entwicklungsarbeit in Einzelschritten und wird beispielsweise bei jedem Kompilieren des Codes um eins erhöht.
Warum ist die automatische Versionierung besser ?
Die manuelle Vergabe von Versionsnummern ist fehleranfällig und zeitaufwändig. Entwickler vergessen häufig, die Version zu aktualisieren, oder wählen inkonsistente Versionsnummern. Durch die Automatisierung der Versionsnummern-Generierung zum Build-Zeitpunkt minimieren bzw. verhindern wir die falsche und fehlende Verwendung der Versionsnummer.
Jedes Deployment-Artefakt muss eine semantische Versionsnummer verwenden. Nur so kann eine Traceability und Nachverfolgbarkeit gewährleistet werden. Wir verwenden hierzu Tags im Commit-Statement, aus denen die semantische Versionsnummer abgeleitet wird.
Vorteile der automatischen Versionierung
- Konsistenz: Alle Artefakte folgen demselben Versionierungsschema
- Nachverfolgbarkeit: Jede Version kann eindeutig einem Commit zugeordnet werden
- Fehlerreduktion: Keine manuellen Fehler bei der Versionsvergabe
- Zeitersparnis: Entwickler mĂĽssen sich nicht um Versionsnummern kĂĽmmern
- Rollback-Fähigkeit: Eindeutige Identifikation für Rollbacks
Unsere Implementierung: Commit-basierte Versionierung
ür unsere Deployment-Artefakte nutzen wir Build-Pipelines unseres Sourcecode-Managementsystems GitLab. Die Versionsnummer wird während des Builds über Commit-Kommentare bestimmt und die Versionsnummern-Vergabe automatisch durchgeführt.
Tagging-Convention zur automatischen Vergabe von Versionsnummern
| Keyword im Commit | Versionsänderung | Beispiel |
|---|---|---|
major | MAJOR +1, MINOR=0, PATCH=0 | 1.0.0 → 2.0.0 |
feature | MINOR +1, PATCH=0 | 1.0.0 → 1.1.0 |
patch, bugfix, fix | PATCH +1 | 1.0.0 → 1.0.1 |
| (kein Keyword) | BUILD +1 | 1.0.0 → 1.0.0-b1 |
Ein Commit mit der Nachricht feature: Neue Exportfunktion hinzugefügt führt automatisch zu einer Minor-Version-Erhöhung. Ein Commit mit bugfix: Fehler in der Datumsvalidierung behoben erhöht die Patch-Version.
Artefakt-Typen und ihre Versionierung
Semantische Versionierung ist nicht auf einen Artefakt-Typ beschränkt. Wir setzen sie für verschiedene Deployment-Artefakte ein:
OCI-Images fĂĽr Microservices
Für containerisierte Microservices (z. B. data-importer, data-receiver) durchläuft unsere CI/CD-Pipeline folgende Schritte:
- Version Calculation Stage:
Dasversion-manager.sh-Skript liest die aktuelle Version aus derrelease-notes.json, analysiert die Commit-Nachricht auf Keywords und berechnet die neue Versionsnummer. - Build Stage: Das OCI-Image wird mit der neuen Versionsnummer gebaut und als
v{VERSION}-{BRANCH}getaggt. - Publish Stage: Das OCI-Image wird in die GitLab Container Registry gepusht.
- Update Stage: Das
update-main-ci.sh-Skript aktualisiert die Versionsvariablen in der.gitlab-ci.ymlund die Release Notes werden automatisch ergänzt. - Commit Stage: Die Änderungen werden automatisch committet mit einer Nachricht wie
chore: update data-importer version to v1.0.1.
JAR-Bibliotheken fĂĽr Maven-Projekte
FĂĽr Java-Bibliotheken, die als JAR-Dateien verteilt werden (z. B. invoice-transformer), ist die semantische Versionierung besonders wichtig. Diese Bibliotheken werden von anderen Projekten als Abhängigkeiten eingebunden – genau hier entsteht das „Dependency Hell“, wenn Versionen nicht korrekt gepflegt werden.
Warum ist die Versionierung von JAR-Artefakten so wichtig?
- Maven-Abhängigkeitsauflösung: Maven verwendet Versionsnummern, um die richtige Version einer Bibliothek zu laden. Ohne eindeutige Versionen können Konflikte entstehen.
- Reproduzierbare Builds: Mit einer festen Versionsnummer kann ein Build jederzeit reproduziert werden.
- Kompatibilitätsprüfung: Entwickler können anhand der Versionsnummer erkennen, ob ein Update Breaking Changes enthält.
- Package Registry Integration: GitLab Package Registry organisiert Pakete nach Versionen – ohne semantische Versionierung ist eine saubere Paketverwaltung unmöglich.
Pipeline-Ablauf fĂĽr JAR-Artefakte:

- Version Calculation: Identisch zu OCI-Images – das
version-manager.sh-Skript berechnet die neue Version. - Build Package: Das
update-pom-version.sh-Skript aktualisiert die Version in derpom.xml, dann wird mitmvn clean packagedas JAR gebaut. - Publish to Registry: Das JAR wird mit
mvn deployin die GitLab Package Registry hochgeladen: - Update Config: Die aktualisierte
pom.xml, das JAR und die Abhängigkeiten werden ins Repository committet.
mvn deploy:deploy-file \
-DgroupId=com.example.invoice \
-DartifactId=invoice-transformer \
-Dversion=${INVOICE_PACKAGE_VERSION} \
-Dpackaging=jar \
-Dfile=target/invoice-transformer.jar \
-Durl=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven
Beispiel einer versionierten pom.xml:
<groupId>com.example.invoice</groupId>
<artifactId>invoice-transformer</artifactId>
<version>1.2.0</version> <!-- Automatisch aktualisiert -->
<packaging>jar</packaging>
Verwendung in anderen Projekten:
<dependency>
<groupId>com.example.invoice</groupId>
<artifactId>invoice-transformer</artifactId>
<version>1.2.0</version>
</dependency>
Release Notes und Kompatibilitätsinformationen
Besonders wertvoll ist die automatische Dokumentation in der release-notes.json. Jeder Build erzeugt einen Eintrag mit Version, Build-Datum, Commit-Nachricht und optional Kompatibilitätsinformationen:
{
"version": "v1.0.0",
"buildAt": "2025-10-23",
"releaseNotes": "Major first release\n- Logging to nfs-share\n- Version management",
"compatibilityInfos": {
"PostgreSQL": "v3.5.3-b1",
"Kafka": "v3.6.1-b1",
"Zookeeper": "v3.8.3-b1"
}
}
Entwickler können Kompatibilitätsinformationen direkt im Commit-Kommentar angeben:
feature: Neue Datenbankanbindung Compatibility: - data-receiver: v1.2.0 or higher - PostgreSQL: v16.0 or higher
Diese Informationen werden automatisch geparst und in der JSON-Datei gespeichert.
Warum Kompatibilitätsinformationen unverzichtbar sind
In verteilten Systemen mit mehreren Microservices ist die Dokumentation von Kompatibilitäten kritisch. Ein typisches Szenario verdeutlicht dies:
Das Szenario: Ein verteiltes System besteht aus mehreren Komponenten, die ĂĽber einen Message Broker miteinander kommunizieren:

Das Problem: Bei einem Deployment wurde eine neue Version des API-Service ausgerollt, die ein geändertes Nachrichtenformat für den Message Broker verwendete. Der Consumer war jedoch noch auf die alte Version konfiguriert und konnte die neuen Nachrichten nicht verarbeiten. Gleichzeitig war der Producer auf eine Version aktualisiert worden, die mit dem alten Consumer-Format inkompatibel war.
Die Folge:
- Nachrichten wurden nicht mehr korrekt verarbeitet
- Fehler traten erst zur Laufzeit auf – nicht beim Build
- Die Fehlersuche war zeitaufwändig, da unklar war, welche Versionen miteinander kompatibel sind
Die Lösung: Durch konsequente Dokumentation der Kompatibilitäten in den Release Notes kann vor einem Deployment geprüft werden, ob alle Komponenten zueinander passen. Hätte das Deployment-Team die Release Notes des neuen API-Service geprüft, wäre sofort ersichtlich gewesen, dass Consumer und Producer ebenfalls aktualisiert werden müssen:
{
"version": "v2.0.0",
"buildAt": "2025-10-28",
"releaseNotes": "Major: Neues Nachrichtenformat fĂĽr Message Broker",
"compatibilityInfos": {
"database": "v3.5.0 or higher",
"message-broker": "v3.6.0 or higher",
"api-service": "v2.0.0",
"consumer": "v2.0.0 or higher",
"producer": "v2.0.0 or higher",
"data-importer": "v1.5.0 or higher"
}
}
Best Practices für Kompatibilitätsinformationen
- Immer dokumentieren
Bei jeder Änderung, die andere Komponenten betrifft, Kompatibilität angeben - Mindestversionen angeben
Nicht nur „kompatibel mit“, sondern „v1.2.0 or higher“ - Breaking Changes markieren
Bei MAJOR-Versionen explizit auf Inkompatibilitäten hinweisen - Infrastruktur einbeziehen
Auch Message Broker, Datenbanken, Cache-Systeme etc. dokumentieren - Vor Deployment prĂĽfen
Release Notes aller zu deployenden Komponenten vergleichen
Helm Values und Versionskonsistenz
In Kubernetes-Deployments mit Helm werden die Image-Versionen in values.yaml definiert. Die Kompatibilitätsinformationen aus den Release Notes helfen dabei, konsistente Deployments zu gewährleisten:
# deployment/values.yaml
api-service:
app:
image: registry.example.com/myproject/api-service:v2.0.0
consumer:
app:
image: registry.example.com/myproject/consumer:v2.0.0
producer:
app:
image: registry.example.com/myproject/producer:v2.0.0
# Alle Versionen mĂĽssen laut Release Notes kompatibel sein!
Vor jedem Deployment sollte geprĂĽft werden:
- Welche Version wird aktuell deployed?
- Welche Versionen laufen bereits in der Zielumgebung?
- Sind alle Versionen laut Release Notes kompatibel?
Tipp: Automatisierte Kompatibilitätsprüfungen können in die CI/CD-Pipeline integriert werden, um vor dem Deployment zu warnen, wenn inkompatible Versionen kombiniert werden sollen.
Abfrage der Versionsnummer zur Laufzeit
Um die Version eines Deployment-Artefaktes abzufragen, stellen wir eine dedizierte, standardisierte API bereit. FĂĽr Microservices in einer Cloud-nativen Architektur erfolgt die Abfrage ĂĽber den Endpunkt /version:
{
"version": "1.0.2",
"build": "abc12345",
"commit_hash": "a1b2c3d4e5f67890abcdef1234567890"
}
Anforderungen an die Versionsabfrage
| Anforderung | Beschreibung |
|---|---|
| Einheitlichkeit | Die Abfrage muss fĂĽr jede Deployment-Artefakt-Kategorie identisch sein |
| Öffentlich zugänglich | Die Abfrage muss ohne Authentifizierung möglich sein |
| Konsistenz | Die Abfrage muss den gleichen Wert liefern wie in Logdateien |
| Lesbarkeit | Die Ausgabe muss natürlich lesbar sein (z. B. UTF‑8‑Text) |
Integration ins Monitoring und Logging
Wir integrieren die Versionsinformationen immer in der Log-Ausgabe des Deployment-Artefaktes. Nur so können die Logeinträge eindeutig der jeweiligen Softwareversion zugeordnet werden.
Anwendungsgebiete
| Bereich | Nutzen |
|---|---|
| Rollback-Strategien | Eindeutige Identifikation des letzten lauffähigen Softwarestandes für schnelle Rollbacks |
| Konsistenz | Standardisierter Ansatz erleichtert Verwaltung und Ăśberwachung komplexer Architekturen |
| Automatisierung | Klare Struktur ermöglicht einfache Automatisierung der Versionsprüfung in CI/CD-Pipelines |
| Debugging | Schnelle Zuordnung von Logeinträgen zur exakten Softwareversion |
| Compliance | Nachweisbarkeit welche Version zu welchem Zeitpunkt im Einsatz war |
Fazit
Die automatisierte semantische Versionierung in GitLab CI/CD ist mehr als nur eine technische Spielerei – sie ist ein fundamentaler Baustein fĂĽr professionelle Softwareentwicklung. Durch die konsequente Anwendung vermeiden wir das gefĂĽrchtete „Dependency Hell“, ermöglichen präzise Rollbacks und schaffen Transparenz ĂĽber den gesamten Software-Lebenszyklus.
Die Investition in ein durchdachtes Versionierungskonzept zahlt sich mehrfach aus:
- Weniger Fehler durch inkonsistente Versionen
- Bessere Nachverfolgbarkeit bei Problemen
- Klare Kommunikation über Änderungen zwischen Teams und Stakeholdern
- Reproduzierbare Builds und Deployments
- Einfache Integration in Package Registries (Maven, npm, Docker)
Ob Docker-Images für Microservices oder JAR-Bibliotheken für Maven-Projekte – das Prinzip bleibt gleich: Jedes Artefakt erhält eine eindeutige, semantisch aussagekräftige Versionsnummer, die automatisch aus dem Commit-Kontext abgeleitet wird.
Quellen und Referenzen
- Die offizielle Spezifikation von Tom Preston-Werner – Semantic Versioning Specification (SemVer)
- Blog des SemVer-Erfinders und GitHub-MitgrĂĽnders – Tom Preston-Werner’s Blog
- Praxisbeispiel zu Dependency Hell – AndrĂ© Monteiro – Semantic Versioning and why it’s important
- Allgemeine Informationen zur Versionierung – Wikipedia – Versionsnummer
- Tutorial zur GitLab-Integration – Medium – Automatic releases in GitLab
- Offizielle GitLab-Dokumentation – GitLab Documentation – Package Registry

