CI/CD
.yml
GitLab · CI/CD · Magento · Deployment-Historie
Release-Tags & Versionierung
nachvollziehbare Deployment-Historie aufbauen

Wer Deployments nicht mit Tags kennzeichnet und keine Versionsnummern pflegt, verliert den Überblick darüber, was wann auf welchem Server gelaufen ist. Release-Tags machen jedes Magento-Deployment eindeutig, rückverfolgbar und im Notfall sofort rollbackfähig.

12 Min. Lesezeit Git Tags · Semantic Versioning · CHANGELOG · GitLab Releases GitLab CI/CD · Magento 2

1. Warum Release-Tags unverzichtbar sind

In vielen Magento-Projekten laufen Deployments direkt aus einem Branch heraus — meist aus main oder master. Das klingt einfach, hat aber einen fundamentalen Nachteil: Kein Deployment ist eindeutig identifizierbar. Wenn ein Fehler nach dem letzten Release auftaucht, lässt sich ohne Tags nicht sofort sagen, welcher Commit tatsächlich auf dem Server läuft. Eine Deployment-Historie ohne Tags ist eine Geschichte ohne Kapitelüberschriften — man sieht zwar, was passiert ist, aber nicht, was wann als abgeschlossenes Release galt.

Ein Release-Tag ist ein unveränderlicher Zeiger auf einen Commit. Er lässt sich nicht versehentlich überschreiben wie ein Branch. Jeder Tag steht für einen klar definierten Stand des Codes, der gebaut, getestet und deployt wurde. In GitLab lässt sich eine Pipeline so konfigurieren, dass sie ausschließlich auf Tags ausgelöst wird — Branches werden geprüft, aber nie direkt in die Produktion deployt. Das trennt Review und Deployment konsequent.

Für Magento-Projekte kommt ein weiterer Aspekt hinzu: Die Deployment-Infrastruktur hält mehrere Release-Verzeichnisse vor. Welches Verzeichnis zu welchem Stand gehört, ergibt sich direkt aus dem Tag. releases/v1.4.2 ist eindeutig, releases/20260509-143012 hingegen erfordert einen Blick in die Logs, um herauszufinden, was sich hinter diesem Zeitstempel verbirgt. Beides ist kombinierbar — Zeitstempel als Verzeichnisname, Tag als Metadatum im Release.

2. Semantic Versioning für Magento-Releases

Das bewährteste Schema für Release-Tags ist Semantic Versioning (SemVer): vMAJOR.MINOR.PATCH. MAJOR wird erhöht, wenn es inkompatible Datenbankmigrationen oder Breaking Changes gibt. MINOR steht für neue Features, die rückwärtskompatibel sind. PATCH kennzeichnet Bugfixes und kleine Korrekturen. Für Magento-Projekte ist dieses Schema besonders wertvoll, weil die Versionsnummer sofort kommuniziert, welche Art von Prüfaufwand vor dem Deployment erwartet werden muss.

In der Praxis empfehlen wir, v als Präfix zu verwenden und Tags in GitLab als Protected Tags zu schützen. Das verhindert, dass Entwickler versehentlich oder absichtlich Tags mit demselben Namen erstellen oder bestehende Tags löschen. Zusätzlich lässt sich in der .gitlab-ci.yml steuern, dass der Deploy-Job nur auf Tags mit dem Muster /^v\d+\.\d+\.\d+$/ reagiert — also nur auf sauber formatierte SemVer-Tags, nicht auf Testläufe oder Hotfix-Tags in einem anderen Format.

# .gitlab-ci.yml — Tag-driven deploy pipeline for Magento
stages:
  - build
  - test
  - package
  - deploy
  - verify
  - rollback

variables:
  GIT_STRATEGY: fetch
  COMPOSER_CACHE_DIR: .cache/composer
  NPM_CONFIG_CACHE: .cache/npm
  # Release ID derived from the git tag (e.g. v1.4.2)
  RELEASE_VERSION: $CI_COMMIT_TAG

# Only run the full deploy pipeline on semver tags
workflow:
  rules:
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
      when: always
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: always
    - when: never

3. Protected Tags und Branch-Governance in GitLab

Die technische Grundlage für eine saubere Deployment-Historie ist eine klare Governance-Konfiguration im GitLab-Projekt. Zunächst werden Branches geschützt: main und release/* akzeptieren keine direkten Pushes, sondern nur Merge Requests. Danach werden Tags als Protected Tags konfiguriert: Nur Maintainer dürfen Tags mit dem Muster v* anlegen. So ist sichergestellt, dass kein Entwickler versehentlich einen Produktions-Deploy auslöst.

Diese Trennung schafft einen definierten Freigabeprozess: Code wird per Merge Request in main integriert, dann nach Tests und Reviews durch einen Maintainer mit einem SemVer-Tag versehen. Erst dieser Tag löst die Produktions-Pipeline aus. Jeder Schritt ist nachvollziehbar, jede Entscheidung protokolliert. Der Git-Log mit Tags ist damit gleichzeitig das Deployment-Protokoll der letzten Wochen.

4. Pipeline nur bei Tags auslösen

In der .gitlab-ci.yml steuert die rules-Direktive, wann ein Job ausgeführt wird. Für den Produktions-Deploy ist das Kriterium klar: nur wenn ein Tag gesetzt ist. Auf Branches läuft lediglich der Build- und Test-Job, niemals der Deploy-Job. Diese Trennung macht Deployments explizit und verhindert, dass ein Push auf main unbeabsichtigt in die Produktion landet.

build:magento:
  stage: build
  image: php:8.4-cli
  script:
    - composer install --no-dev --prefer-dist --no-interaction
    - npm ci --prefix app/design/frontend/Mironsoft/default/web/tailwind
    - npm run build --prefix app/design/frontend/Mironsoft/default/web/tailwind
    - php bin/magento setup:di:compile
  artifacts:
    paths:
      - vendor/
      - generated/
      - pub/static/
    expire_in: 1 day
  # Build runs on branches and tags alike
  rules:
    - if: $CI_COMMIT_TAG
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

deploy:production:
  stage: deploy
  environment:
    name: production
    url: https://shop.example.com
  script:
    - ./scripts/deploy.sh
  # Deploy ONLY on semver tags — never on branches
  rules:
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
      when: manual
      allow_failure: false

5. Release-ID aus dem Tag ableiten

Das Deploy-Skript erhält den Tag-Namen als Umgebungsvariable $CI_COMMIT_TAG und nutzt ihn als Teil des Release-Verzeichnisnamens auf dem Zielserver. So entstehen Verzeichnisse wie releases/v1.4.2, die sofort klar machen, welcher Release dort liegt. Alternativ lässt sich auch ein Zeitstempel-basierter Pfad verwenden und das Tag als Symlink oder Metadatei speichern — beide Ansätze sind kombinierbar.

Wichtig ist, dass der Tag-Name nie manipuliert oder normiert werden muss, bevor er als Verzeichnisname genutzt wird. Ein sauber definiertes Tag-Format wie v1.4.2 ist als Pfadkomponente vollständig geeignet. Das Deployment-Skript prüft, ob das Zielverzeichnis bereits existiert — falls ja, bricht es ab, um doppelte Releases zu vermeiden. Dieser Mechanismus macht Tags zu einer einmaligen, unveränderlichen Referenz auch auf Serverebene.

# scripts/deploy.sh — Deploy a tagged Magento release to the server
#!/usr/bin/env bash
set -euo pipefail

# Use the git tag as the release identifier
readonly RELEASE_VERSION="${CI_COMMIT_TAG:?CI_COMMIT_TAG is not set}"
readonly APP_PATH="${DEPLOY_PATH:?DEPLOY_PATH is not set}"
readonly RELEASE_PATH="${APP_PATH}/releases/${RELEASE_VERSION}"

# Abort if this release already exists on the server
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "test ! -d '${RELEASE_PATH}'" \
  || { echo "[ERROR] Release ${RELEASE_VERSION} already deployed"; exit 1; }

# Transfer the build artifact to the new release directory
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p '${RELEASE_PATH}'"
rsync -az --delete ./ "${DEPLOY_USER}@${DEPLOY_HOST}:${RELEASE_PATH}/"

# Link shared directories and switch the current symlink atomically
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s <<SSH
set -euo pipefail
ln -sfn "${APP_PATH}/shared/app/etc/env.php" "${RELEASE_PATH}/app/etc/env.php"
ln -sfn "${APP_PATH}/shared/pub/media" "${RELEASE_PATH}/pub/media"
ln -sfn "${RELEASE_PATH}" "${APP_PATH}/current"
echo "[OK] Switched current to ${RELEASE_VERSION}"
SSH

6. GitLab Release Object anlegen

GitLab bietet seit einigen Versionen das Konzept von Releases als eigenes Objekt im Projekt. Ein Release ist mehr als ein Tag — er kann Release Notes, Links zu Artefakten und einen Changelog enthalten. Diese Informationen erscheinen in der GitLab-Oberfläche unter "Deployments > Releases" und bilden damit eine automatisch gepflegte Deployment-Historie, die für alle Teammitglieder sichtbar ist.

Das Anlegen eines GitLab Release Objects geschieht im Pipeline-Job mit dem release-cli-Tool, das GitLab als Docker-Image bereitstellt. Der Job liest den Changelog für die aktuelle Version aus einer Datei oder generiert ihn aus den Git-Commit-Messages seit dem letzten Tag. So entsteht eine vollständige, strukturierte Deployment-Historie ohne manuellen Aufwand — jedes Mal, wenn ein neuer SemVer-Tag gepusht wird.

create:release:
  stage: package
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  script:
    - echo "Creating GitLab Release for ${CI_COMMIT_TAG}"
  release:
    name: "Release ${CI_COMMIT_TAG}"
    tag_name: "${CI_COMMIT_TAG}"
    description: |
      ## Magento Release ${CI_COMMIT_TAG}

      Deployed: $(date -u +"%Y-%m-%d %H:%M UTC")
      Commit: ${CI_COMMIT_SHA}
      Pipeline: ${CI_PIPELINE_URL}
    assets:
      links:
        - name: "Deployment Log"
          url: "${CI_PIPELINE_URL}"
  rules:
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/

7. CHANGELOG und Deployment-Log führen

Eine Deployment-Historie entsteht nicht allein durch Tags — sie erfordert auch dokumentierte Inhalte pro Release. Das Minimum ist eine CHANGELOG.md-Datei im Repository, die nach dem Format "Keep a Changelog" gepflegt wird. Jede Versionssektion enthält die wichtigsten Änderungen: neue Features, Bugfixes, Datenbankmigrationen und Breaking Changes. Diese Datei wird im GitLab Release Object verlinkt und ist damit direkt aus der GitLab-Oberfläche erreichbar.

Zusätzlich zum CHANGELOG empfiehlt sich ein serverseitiges Deployment-Log: Eine Datei RELEASES.log im gemeinsamen shared-Verzeichnis, in die jedes Deployment-Skript automatisch eine Zeile schreibt — mit Zeitstempel, Version, Commit-SHA und dem Namen des auslösenden Users. So lässt sich auch ohne Zugriff auf GitLab direkt auf dem Server nachvollziehen, was wann deployt wurde. Diese einfache Maßnahme verhindert im Notfall das ratlose "Wann war das letzte Deployment und was war drin?"

8. Versionierung im Vergleich: chaotisch vs. strukturiert

Der Unterschied zwischen einem Projekt mit und ohne konsequente Release-Tags zeigt sich vor allem im Störfall. Ohne Tags muss man im Git-Log nach dem letzten Commit suchen, der auf dem Server läuft, den Deployment-Zeitpunkt aus Logs rekonstruieren und manuell entscheiden, auf welchen Stand zurückgerollt werden soll. Mit Tags dauert dasselbe: git tag --sort=-creatordate | head -5 zeigt die letzten Releases, und Rollback bedeutet das Umschalten auf ein bereits vorhandenes Release-Verzeichnis.

Aspekt Ohne Tags (chaotisch) Mit SemVer-Tags (strukturiert) Vorteil
Release-Identifikation Commit-SHA oder Zeitstempel v1.4.2 — eindeutig, lesbar Sofort nachvollziehbar
Produktions-Deploy auslösen Jeder Push auf main Nur auf Protected Tags Explizite Freigabe
Rollback-Ziel bestimmen Log durchsuchen, raten releases/v1.4.1 vorhanden Sofortiger Rollback
Deployment-Historie Nicht vorhanden GitLab Releases + CHANGELOG Vollständig, navigierbar
Kommunikation im Team "Das letzte Deployment" "v1.4.2 vom 09.05." Klare Referenz

9. Zusammenfassung

Release-Tags sind keine bürokratische Pflicht, sondern das Fundament jeder nachvollziehbaren Deployment-Historie. Ein SemVer-Tag ist der einzige Mechanismus, der einen Commit unwiderruflich als freigegebenen Release kennzeichnet. In GitLab lässt sich dieser Prozess vollständig automatisieren: Tags lösen Pipelines aus, Release Objects werden angelegt, Release-Verzeichnisse auf dem Server tragen den Tag-Namen, und das CHANGELOG liefert den inhaltlichen Überblick pro Version.

Die Investition in eine konsequente Versionierungsstrategie zahlt sich nicht im normalen Betrieb aus — sondern genau dann, wenn etwas schiefläuft. Im Störfall entscheidet das Vorhandensein einer sauberen Deployment-Historie darüber, ob ein Rollback Minuten oder Stunden dauert. Wer Magento-Deployments ohne Tags betreibt, hat keinen Rollback-Plan, sondern nur die Hoffnung, dass alles gut geht.

Release-Tags und Deployment-Historie — Das Wichtigste auf einen Blick

Tag-Format

SemVer-Tags v1.4.2 als Protected Tags in GitLab — nur Maintainer dürfen sie anlegen. Pipeline reagiert nur auf dieses Format.

Pipeline-Steuerung

Deploy-Jobs mit rules: if: $CI_COMMIT_TAG absichern. Branches bauen und testen, aber nie direkt deployen.

Release-Verzeichnisse

Tag-Name als Verzeichnisname verwenden: releases/v1.4.2. Verhindert doppelte Deployments und macht Rollback trivial.

Deployment-Historie

GitLab Release Objects + CHANGELOG.md + serverseitiges RELEASES.log — drei Ebenen für vollständige Nachvollziehbarkeit.

10. Typische Fehler bei Release-Tags

Der häufigste Fehler ist das Erstellen von Tags direkt auf dem Branch, ohne dass Tests und Quality Checks bestanden haben. Ein Protected Tag allein reicht nicht — der Prozess muss so gestaltet sein, dass Tags erst nach einem erfolgreichen Pipeline-Durchlauf auf dem Release-Branch gesetzt werden. Wer Tags als ersten Schritt setzt und dann erst testet, hat die Kausalität umgekehrt und deployt im Zweifel defekten Code.

Ein zweiter Fehler betrifft die Granularität: Manche Teams taggen jeden Commit, andere nur Major-Releases. Beides ist suboptimal. Die richtige Balance liegt bei einer klaren Konvention — zum Beispiel Minor-Releases für alle Feature-Deployments, Patch-Releases für Bugfixes und Hotfixes, Major-Releases für Deployments mit Datenbankmigrationen oder Breaking Changes. Diese Konvention muss dokumentiert und bekannt sein, damit das Team Tags einheitlich und aussagekräftig setzt.

11. FAQ: Release-Tags, Versionierung und Deployment-Historie

1Muss ich SemVer verwenden?
SemVer ist empfohlen, weil es sofort kommuniziert, welche Art von Änderungen ein Release enthält. Ein eigenes Format funktioniert, solange es konsistent und als Protected Tag gesetzt ist.
2Wie verhindere ich Tags ohne bestandene Tests?
Mit Release-Branch-Workflow: Erst mergen, Pipeline grün abwarten, dann taggen. Der Tag löst den Deploy-Job aus — der Test-Job hat bereits bestanden.
3Kann ich denselben Tag zweimal deployen?
Das Skript prüft, ob das Release-Verzeichnis existiert, und bricht bei Dopplung ab. Jeder echte Release braucht eine neue Versionsnummer.
4Git-Tag vs. GitLab Release?
Git-Tag = technischer Zeiger auf einen Commit. GitLab Release = sichtbares Objekt mit Release Notes, Links und Changelog. Beides zusammen ergibt eine vollständige Historie.
5Wie lange alte Releases behalten?
Mindestens 3–5 Releases, damit im Störfall sofort zurückgerollt werden kann. Ältere Releases nach konfigurierbarer Retentionzeit automatisch löschen.
6Datenbankmigrationen in der Historie dokumentieren?
Im CHANGELOG als Breaking Change mit Major-Version-Increment und im GitLab Release Object als expliziter Hinweis. Entscheidend für die Rollback-Strategie.
7Tags für Staging-Deployments?
Mit eigenem Muster: z.B. rc-Tags für Release Candidates. Gleicher Mechanismus, anderes Tag-Format, um Staging und Production klar zu trennen.
8CHANGELOG in GitLab Release integrieren?
Mit release-cli im Pipeline-Job: Entsprechende Sektion aus CHANGELOG.md lesen und als description-Parameter übergeben. Alternativ Commit-Messages seit letztem Tag auslesen.
9Was passiert wenn der Deploy-Job fehlschlägt?
Das Release-Verzeichnis bleibt unvollständig, current zeigt noch auf das vorherige Release. Kein Schaden am laufenden System. Skript muss idempotent sein.
10Reicht ein RELEASES.log als Deployment-Historie?
Als serverseitiger Notfallnachweis ja. Vollständige Historie entsteht erst durch Kombination: Git-Tags + GitLab Releases + CHANGELOG + serverseitiges Log.