logo

Extra Block Types (EBT) - Neue Erfahrung im Layout Builder❗

Extra Block Types (EBT) - gestylte, anpassbare Blocktypen: Diashows, Registerkarten, Karten, Akkordeons und viele andere. Eingebaute Einstellungen für Hintergrund, DOM Box, Javascript Plugins. Erleben Sie die Zukunft der Layouterstellung schon heute.

Demo EBT-Module EBT-Module herunterladen

❗Extra Absatztypen (EPT) - Erfahrung mit neuen Absätzen

Extra Paragraph Types (EPT) - analoger, auf Absätzen basierender Satz von Modulen.

Demo EPT-Module EPT-Module herunterladen

GLightbox is a pure javascript lightbox (Colorbox alternative without jQuery)❗

It can display images, iframes, inline content and videos with optional autoplay for YouTube, Vimeo and even self-hosted videos.

Demo GLightbox Download GLightbox

Scroll

CI‑gesteuertes Drupal‑Konfigurationsmanagement mit Jenkins und GitLab CI

16/04/2026, by Ivan

1. Warum CI‑gesteuertes Konfigurationsmanagement wichtig ist

Drupals Konfigurationssystem ist eine der größten Stärken der Plattform — und eine ihrer zuverlässigsten Schmerzquellen. Die Möglichkeit, jede einzelne Site‑Konfiguration als YAML‑Dateien zu exportieren und zu importieren, ist mächtig, aber nur dann, wenn alle sich einig sind, wer dafür verantwortlich ist, diese Dateien zwischen den Umgebungen zu bewegen. In den meisten Teams existiert diese Einigung nie so richtig.

Die klassischen Probleme sind jedem bekannt, der bereits eine Drupal‑Site ausgeliefert hat:

  • Config Drift — Staging weicht von Production ab, Production weicht von Local ab, und niemand weiß, welche Umgebung kanonisch ist.
  • „Funktioniert auf Staging, aber nicht auf Prod“ — weil jemand auf Staging eine View oder einen Feld‑Formatter aktualisiert und diese Änderung nie exportiert hat.
  • Manuelles drush cim zerstört Inhalte — ein hastiger Import um 23 Uhr, der ein Feld eines Inhaltstyps gelöscht hat, auf das Live‑Nodes noch verwiesen.

Die Grundursache all dieser Szenarien ist dieselbe: Ein Mensch entscheidet, wann und ob Konfiguration promoted wird. Menschen vergessen Dinge. Unter Druck überspringen sie Schritte. Sie treffen Entscheidungen, die sich im Nachhinein als falsch herausstellen.

CI vergisst nicht. Eine Pipeline besteht oder schlägt fehl. Sie muss nicht zu einem Standup. Sie weiß nicht, dass der Release in zwanzig Minuten ist. Genau diese Deterministik braucht das Konfigurationsmanagement.

Das Versprechen dieses Artikels:

  • Jede Konfigurationsänderung wird in Git committed, bevor sie eine gemeinsam genutzte Umgebung erreicht.
  • Die Pipeline, nicht ein Entwickler, ist für die Validierung und den Import der Konfiguration verantwortlich.
  • Die Promotion zwischen Umgebungen erfordert keinerlei manuelle Schritte.
  • Config Drift ist ein Build‑Fehler — keine Slack‑Nachricht.
Annahmen: Sie verwenden Drupal 10 oder 11, arbeiten mit einem Git‑basierten Workflow mit Feature‑Branches und deployen in mindestens zwei gemeinsam genutzte Umgebungen (z. B. Staging und Production). Ihr Team verwendet entweder Jenkins oder GitLab CI — oder beides.

2. Zentrale Prinzipien aus realen Projekten

Konfiguration ist Code

Wenn es das Verhalten der Site verändert, gehört es in Git. Punkt. Eine View, ein Inhaltstyp, eine Performance‑Einstellung, ein Bildstil — all das ist Code. Behandeln Sie Konfigurationsdateien mit derselben Disziplin wie PHP‑Dateien: Prüfen Sie sie im Review, versionieren Sie sie, und bearbeiten Sie sie niemals direkt in einer gemeinsam genutzten Umgebung.

Kein manuelles drush cim in gemeinsam genutzten Umgebungen

Die Pipeline importiert die Konfiguration. Entwickler tun das nicht. Diese Regel klingt extrem — bis zu dem ersten Mal, wenn jemand drush cim auf Production ausführt, während sich noch nicht commitete lokale Änderungen in seinem Working Directory befinden.

Pipelines müssen bei Config Drift schnell fehlschlagen

Konfiguration zu importieren und anschließend sofort wieder zu exportieren, sollte keinen Diff erzeugen. Falls doch, schlägt der Build fehl. Diese eine Regel fängt mehr Bugs ab als jede andere Prüfung, die wir hinzugefügt haben.

Ein Senior‑Entwickler debugte eine langsame Suchseite auf Production. Er passte die Search‑API‑Index‑Einstellungen über die UI an, bestätigte die verbesserte Performance und schloss das Ticket. Zwei Wochen später importierte ein Release die Konfiguration aus Git — und überschieb seine UI‑Änderungen — die Such‑Performance brach ein. Drei Tage lang brachte niemand das in Zusammenhang. Nach diesem Vorfall machten wir die Regel explizit: Jede UI‑Änderung, die nicht sofort exportiert und committed wird, gilt als verloren.
Auf einer medienintensiven Site wurde core.extension.yml von den Exporten ausgeschlossen, weil „Module ohnehin von Composer verwaltet werden“. Drei Monate später aktivierte ein Hotfix ein Modul wieder, das in Production absichtlich deaktiviert worden war. Der Hotfix war korrekt — doch ein nachfolgender Config‑Import deaktivierte das Modul stillschweigend erneut. Seit diesem Vorfall nennen wir core.extension.yml die gefährlichste Datei, die man ignorieren kann.

3. High-Level-Architektur

👨‍💻 Entwickler
drush cex
📁 Git-Repository
config/sync
⚙ CI-Pipeline
Validierung + Import
🌐 Entwicklung
🌐 Staging
🌐 Produktion

Die Konfiguration fließt nur in eine Richtung: vom lokalen Export des Entwicklers, über Git, durch die CI-Pipeline und in jede Umgebung.

Die zentrale Erkenntnis ist die Trennung der Verantwortlichkeiten:

AkteurVerantwortungNiemals verantwortlich für
EntwicklerKonfiguration exportieren, in Git committen, MR/PR eröffnenImportieren von Konfiguration in einer gemeinsam genutzten Umgebung
CI-PipelineValidieren, importieren, verifizieren, promotenErzeugen oder Bearbeiten von Konfigurationsdateien
UmgebungAusführen der SiteErzeugen, Speichern oder Exportieren von Konfiguration

Umgebungen sind Konsumenten der Konfiguration, niemals Produzenten. In dem Moment, in dem eine Umgebung zur Quelle der Wahrheit für Konfiguration wird, ist Drift unvermeidlich.

4. Skalierbare Repository-Struktur

Directory Tree project root
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│   ├── drupal-config-check.sh   # Wiederverwendbares Validierungsskript
│   ├── drupal-deploy.sh
│   └── drupal-install.sh
│
├── config/
│   ├── sync/                        # Konfiguration liegt hier — in Git committed
│   │   ├── core.extension.yml
│   │   ├── system.site.yml
│   │   └── ...
│   └── splits/                      # config_split Überschreibungen pro Umgebung
│       ├── development/
│       ├── staging/
│       └── production/
│
└── web/
    ├── sites/
    │   └── default/
    │       ├── settings.php          # Committed, keine Secrets
    │       ├── settings.local.php    # Gitignoriert, lokale Overrides
    │       └── settings.env.php      # Von CI aus Env-Variablen geladen
    └── ...

Was sich in config/sync befindet

Alle Konfigurations-YAML-Dateien, die von drush cex erzeugt werden. Dieses Verzeichnis ist die einzige Quelle der Wahrheit für die Site-Konfiguration. Es wird wie jeder andere Code committed, überprüft und deployt.

Was niemals in die Konfiguration gehört

  • Umgebungsspezifische Hostnamen, API-Keys oder Zugangsdaten — verwenden Sie Umgebungsvariablen und settings.env.php.
  • Secrets jeglicher Art — über Ihren CI-Secret-Store injizieren (Jenkins Credentials oder GitLab CI Variables).
  • Content — verwenden Sie das Default-Content-Modul nicht als Krücke für etwas, das eigentlich Konfiguration sein sollte.
PHP web/sites/default/settings.php
// settings.php — in Git committed, umgebungsneutral
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';

// Umgebungsspezifische Werte laden, die von CI oder dem Host injiziert wurden.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
  include $app_root . '/' . $site_path . '/settings.env.php';
}

// Optionale lokale Overrides laden (gitignoriert).
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
  include $app_root . '/' . $site_path . '/settings.local.php';
}
PHP web/sites/default/settings.env.php
// Von CI aus Umgebungsvariablen generiert — niemals committen.
$databases['default']['default'] = [
  'driver'   => 'mysql',
  'host'     => getenv('DB_HOST'),
  'database' => getenv('DB_NAME'),
  'username' => getenv('DB_USER'),
  'password' => getenv('DB_PASS'),
  'port'     => getenv('DB_PORT') ?: 3306,
  'prefix'   => '',
];

$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT');

// config_split mitteilen, in welcher Umgebung wir uns befinden.
$config['config_split.config_split.production']['status'] =
  (getenv('APP_ENV') === 'production');

5. Drupal-Konfigurationsworkflow

5.1 Lokale Entwicklung

Der lokale Workflow ist der einzige Ort, an dem die Feedback-Schleife zwischen UI-Änderungen und Konfigurationsexport schnell und zur Gewohnheit werden muss. Jeder Entwickler im Projekt folgt dabei denselben Regeln:

  • UI- oder Code-Änderungen lokal durchführen.
  • Unmittelbar danach drush cex ausführen — nicht erst am Ende des Tages.
  • Die Änderungen mit git diff config/sync überprüfen.
  • Änderungen entweder committen oder mit git checkout -- config/sync verwerfen.
  • Einen Zwischenstatus gibt es nicht.
Shell Lokaler Entwicklungsworkflow
# Nach Konfigurationsänderungen über die Drupal-UI oder Install-Hooks:
drush cex --yes

# Änderungen prüfen — wie jede andere Code-Änderung behandeln:
git diff config/sync

# Zusammen mit dem Feature-Code vormerken und committen:
git add config/sync
git commit -m "feat(search): add fulltext search API index config"

# Oder verwerfen, wenn die Änderung explorativ war und noch nicht bereit ist:
git checkout -- config/sync

Dies wird durch einen schlanken Git-Pre-Commit-Hook unterstützt, der warnt (aber nicht blockiert), wenn PHP- oder Template-Dateien vorgemerkt wurden, ohne eine entsprechende Änderung in config/sync:

Shell .git/hooks/pre-commit
#!/bin/bash
# Warnung, wenn Modul-/Theme-Code geändert wurde, aber config/sync nicht.
CHANGED_CODE=$(git diff --cached --name-only | grep -E '\.(php|module|theme|install)$')
CHANGED_CONFIG=$(git diff --cached --name-only | grep '^config/sync')

if [[ -n "$CHANGED_CODE" ]] && [[ -z "$CHANGED_CONFIG" ]]; then
  echo "⚠  Warnung: PHP-/Moduldateien wurden vorgemerkt, aber keine Änderungen in config/sync erkannt."
  echo "   Haben Sie vergessen: drush cex auszuführen?"
  echo "   Es wird trotzdem fortgefahren — bitte überprüfen."
fi
exit 0

5.2 Feature-Branches

Konfiguration wird gemeinsam mit dem Code versioniert, der sie benötigt — im selben Branch und in derselben Pull-/Merge-Request. Ein Feature, das beispielsweise einen neuen Inhaltstyp hinzufügt, sollte sowohl den PHP-Install-Hook (falls vorhanden) als auch die YAML-Dateien für diesen Inhaltstyp im selben Commit enthalten.

Defekte Abhängigkeiten frühzeitig erkennen: Wenn Branch A ein neues Feld hinzufügt und Branch B die Anzeige desselben Feldes ändert, führt das Mergen von B vor A zu einem Konfigurationsimportfehler. Das ist genau das richtige Ergebnis — die Pipeline soll dies im Branch erkennen, nicht erst in der Produktion.

6. CI-Verantwortlichkeiten: Was die Pipeline erzwingen muss

Jede Pipeline, die mit einer gemeinsam genutzten Drupal-Umgebung arbeitet, muss die folgenden Schritte in genau dieser Reihenfolge ausführen:

  1. Datenbank installieren oder wiederherstellen — entweder eine saubere Installation oder einen bereinigten Produktions-Snapshot.
  2. Datenbank-Updates ausführendrush updb.
  3. Konfiguration importierendrush cim --yes.
  4. Konfiguration erneut exportierendrush cex --yes.
  5. Sicherstellen, dass kein Diff vorhanden ist — wenn sich eine YAML-Datei geändert hat, muss der Build fehlschlagen.

Schritt 4 und 5 zusammen sind am wichtigsten. Nach einem Import darf ein unmittelbarer Re‑Export keinen Diff erzeugen. Falls doch, trifft eines der folgenden Szenarien zu:

  • Ein Modul generiert beim Import Konfiguration (häufig ein Modul-Bug).
  • Die UUID einer Konfigurationsentität stimmt nicht mit der in der Datenbank überein.
  • Ein Konfigurationsschema ist unvollständig, sodass Drupal Werte anders normalisiert als beim Export.
  • Ein Entwickler hat Konfigurationsdateien manuell bearbeitet und dadurch Inkonsistenzen eingeführt.
Shell ci/drupal-config-check.sh
#!/bin/bash
set -euo pipefail

echo "=== Drupal-Datenbank-Updates werden ausgeführt ==="
drush updb --yes

echo "=== Konfiguration aus config/sync wird importiert ==="
drush cim --yes

echo "=== Re-Export zur Überprüfung auf ausstehende Änderungen ==="
drush cex --yes

echo "=== Überprüfung auf Konfigurationsdrift ==="
if ! git diff --exit-code config/sync; then
  echo ""
  echo "❌ FEHLER: Konfigurationsdrift erkannt!"
  echo "   Die Konfiguration in Git stimmt nicht mit der überein,"
  echo "   die Drupal nach Import+Export erzeugt hat."
  echo "   Diff oben angezeigt. Lokal mit 'drush cex' beheben und committen."
  exit 1
fi

echo "✅ Konfiguration ist konsistent — kein Drift erkannt."
Warum der Re‑Export nach dem Import unverzichtbar ist: Ohne den Re‑Export-Schritt wissen Sie lediglich, dass der Import erfolgreich war. Sie wissen jedoch nicht, ob die importierte Konfiguration mit dem übereinstimmt, was Drupal intern tatsächlich gespeichert hat. Erst Re‑Export und anschließender Diff-Vergleich schließen diese Lücke.
 

7. Jenkins-Implementierung (Praxisbewährt)

7.1 Jenkinsfile-Struktur

Wir verwenden ausschließlich deklarative Pipelines. Scripted Pipelines bieten zwar mehr Flexibilität, aber deklarative Pipelines sind leichter lesbar, lassen sich einfacher mit jenkins-cli declarative-linter prüfen und sind für neue Teammitglieder verständlicher.


pipeline {
  agent { label 'drupal-php82' }

  options {
    buildDiscarder(logRotator(numToKeepStr: '20'))
    timeout(time: 30, unit: 'MINUTES')
    disableConcurrentBuilds()
  }

  environment {
    DRUPAL_ROOT       = "${WORKSPACE}/web"
    COMPOSER_HOME     = "${WORKSPACE}/.composer"
    APP_ENV           = 'ci'
    DB_CREDS          = credentials('drupal-ci-db') // Jenkins Secret
  }

  stages {

    stage('Checkout') {
      steps {
        checkout scm
        sh 'git log --oneline -5'
      }
    }

    stage('Composer Install') {
      steps {
        sh '''
          composer install \
            --no-interaction \
            --prefer-dist \
            --optimize-autoloader
        '''
      }
    }

    stage('Write Settings') {
      steps {
        // settings.env.php aus Jenkins-Credentials/Env-Variablen generieren
        sh '''
          cat > web/sites/default/settings.env.php <<EOF
$databases['default']['default'] = [
  'driver'   => 'mysql',
  'host'     => '127.0.0.1',
  'database' => 'drupal_ci',
  'username' => '${DB_CREDS_USR}',
  'password' => '${DB_CREDS_PSW}',
  'port'     => 3306,
  'prefix'   => '',
];
$settings['hash_salt'] = '${DRUPAL_HASH_SALT}';
EOF
        '''
      }
    }

    stage('Site Install') {
      steps {
        sh '''
          drush site-install minimal \
            --yes \
            --existing-config \
            --account-name=admin \
            --account-pass="${DRUPAL_ADMIN_PASS}"
        '''
      }
    }

    stage('Validate Config') {
      steps {
        sh 'bash ci/drupal-config-check.sh'
      }
      post {
        failure {
          sh 'git diff config/sync || true'
          archiveArtifacts artifacts: 'config/sync/**/*.yml', allowEmptyArchive: true
        }
      }
    }

    stage('Deploy') {
      when {
        anyOf {
          branch 'main'
          branch 'release/*'
        }
      }
      steps {
        sh 'bash ci/drupal-deploy.sh'
      }
    }

  }

  post {
    always {
      sh 'drush cr || true'
      cleanWs()
    }
    failure {
      emailext(
        subject: "[FAIL] ${JOB_NAME} #${BUILD_NUMBER}",
        body: "Konfigurationsvalidierung fehlgeschlagen. Siehe: ${BUILD_URL}",
        recipientProviders: [[$class: 'DevelopersRecipientProvider']]
      )
    }
  }
}

7.2 Jenkins-spezifische Überlegungen

Shared Libraries

Wenn mehr als zwei oder drei Drupal-Sites verwaltet werden, sollte die Drupal-Pipeline-Logik in eine Jenkins Shared Library ausgelagert werden. Das Jenkinsfile jedes Projekts wird dann zu einem schlanken Wrapper:

Groovy Jenkinsfile (schlanker Wrapper mit Shared Library)
@Library('drupal-pipeline-lib@v2') _

drupalPipeline(
  phpVersion:    '8.2',
  deployBranch:  'main',
  dbCredentials: 'drupal-ci-db',
  slackChannel:  '#deployments'
)

Workspace-Bereinigung

Rufen Sie immer cleanWs() im Block post { always } auf. Jenkins-Agenten sammeln Zustand aus vorherigen Builds — eine settings.env.php oder ein Vendor-Verzeichnis aus einem früheren Build kann den aktuellen Build unbemerkt beeinflussen. Seien Sie konsequent: Bereinigen Sie den Workspace.

Verwenden Sie niemals eine Datenbank zwischen Pipeline-Läufen wieder, es sei denn, Sie testen explizit Upgrade-Pfade. Eine übrig gebliebene Datenbank aus einem früheren Build kann UUID-Mismatches, fehlende Migrationen oder Probleme bei der Installationsreihenfolge von Modulen verschleiern.

8. GitLab-CI-Implementierung

8.1 .gitlab-ci.yml-Struktur

YAML .gitlab-ci.yml
image: php:8.2-cli

stages:
  - build
  - test
  - validate-config
  - deploy

variables:
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
  APP_ENV: "ci"
  MYSQL_DATABASE: "drupal_ci"
  MYSQL_ROOT_PASSWORD: "root"

.cache-composer: &cache-composer
  cache:
    key:
      files: [composer.lock]
    paths:
      - .cache/composer
      - vendor/
    policy: pull

composer:install:
  stage: build
  cache:
    key:
      files: [composer.lock]
    paths:
      - .cache/composer
      - vendor/
    policy: pull-push
  script:
    - composer install --no-interaction --prefer-dist --optimize-autoloader
  artifacts:
    paths:
      - vendor/
      - web/core/
      - web/modules/contrib/
    expire_in: 1 hour

phpunit:unit:
  stage: test
  <<: *cache-composer
  script:
    - ./vendor/bin/phpunit --testsuite=unit --log-junit=reports/phpunit.xml
  artifacts:
    reports:
      junit: reports/phpunit.xml

drupal:validate-config:
  stage: validate-config
  <<: *cache-composer
  services:
    - name: mysql:8.0
      alias: mysql
  variables:
    DB_HOST: mysql
    DB_NAME: $MYSQL_DATABASE
    DB_USER: root
    DB_PASS: $MYSQL_ROOT_PASSWORD
  before_script:
    - bash ci/write-settings-env.sh
    - drush site-install minimal --yes --existing-config
  script:
    - bash ci/drupal-config-check.sh
  artifacts:
    when: on_failure
    paths:
      - config/sync/
    expire_in: 3 days

deploy:staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
  script:
    - bash ci/drupal-deploy.sh staging

deploy:production:
  stage: deploy
  environment:
    name: production
    url: https://example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
  script:
    - bash ci/drupal-deploy.sh production

8.2 Vorteile von GitLab CI

GitLab CI bietet mehrere Funktionen, die diesen Workflow besonders sauber unterstützen:

  • Erstklassige Artefakte — Fehlgeschlagene Konfigurationsexporte werden automatisch gespeichert und sind in der MR-Oberfläche ohne zusätzliche Konfiguration einsehbar.
  • Native Caching basierend auf composer.lock — Composer-Installationen sind nach dem ersten Lauf günstig.
  • Merge-Request-Pipeline-Transparenz — Entwickler sehen den Status der Konfigurationsvalidierung direkt im MR vor dem Merge.
  • Services-Block — MySQL startet als Sidecar ohne Infrastruktur-Overhead.
  • Environments + manuelle Gates — Die when: manual-Regel beim Produktions-Deploy ermöglicht One-Click-Promotion mit menschlicher Freigabe, ohne einen separaten Genehmigungsworkflow.
Shell ci/drupal-deploy.sh
#!/bin/bash
set -euo pipefail

TARGET_ENV="${1:-staging}"
echo "=== Deployment nach: ${TARGET_ENV} ==="

rsync -az --delete \
  --exclude='.git' \
  --exclude='web/sites/default/files' \
  --exclude='web/sites/default/settings.env.php' \
  ./ "deploy@${TARGET_ENV}.example.com:/var/www/drupal/"

echo "=== Post-Deploy-Befehle auf ${TARGET_ENV} werden ausgeführt ==="
ssh "deploy@${TARGET_ENV}.example.com" bash -s <<'REMOTE'
  set -e
  cd /var/www/drupal
  drush updb --yes
  drush cim --yes
  drush cex --yes
  git diff --exit-code config/sync || (echo "❌ Config Drift im Zielsystem!"; exit 1)
  drush cr
  echo "✅ Deployment abgeschlossen."
REMOTE

9. Umgebungsspezifische Konfiguration ohne Tricks

Nicht jeder Konfigurationswert sollte in allen Umgebungen identisch sein. Such-Backends, Logging-Verbosity, Caching-Layer und Drittanbieter-API-Endpunkte unterscheiden sich berechtigterweise. Die Frage ist wie diese Unterschiede gehandhabt werden können, ohne die Integrität der Config-Pipeline zu kompromittieren.

Die richtigen Werkzeuge: config_split und config_ignore

config_split ermöglicht es, Konfigurationssätze zu definieren, die nur in bestimmten Umgebungen aktiv sind. Jeder Split befindet sich in seinem eigenen Verzeichnis und wird über settings.php oder settings.env.php basierend auf einer Umgebungsvariable aktiviert.

YAML config/sync/config_split.config_split.development.yml
langcode: en
status: true
id: development
label: Development
description: 'Konfiguration nur in lokalen/dev-Umgebungen aktiv'
folder: '../config/splits/development'
module:
  devel: 0
  kint: 0
  dblog: 0
theme: {  }
blacklist: {  }
graylist: {  }
PHP web/sites/default/settings.env.php (Umgebungsaktivierung)
$app_env = getenv('APP_ENV') ?: 'production';

// Den passenden config_split basierend auf der Umgebung aktivieren.
$config['config_split.config_split.development']['status']  = ($app_env === 'development');
$config['config_split.config_split.staging']['status']     = ($app_env === 'staging');
$config['config_split.config_split.production']['status']   = ($app_env === 'production');

Beispiele aus der Praxis für Splits

KonfigurationselementDev-SplitStage-SplitProd-Split
Search API BackendDatenbank-BackendSolr (klein)Solr (Produktions-Cluster)
Fehlerprotokollierungdblog, ausführlichsyslogsyslog + externes APM
Performance-ModuleDeaktiviertAktiviertAktiviert + CDN-Konfiguration
Mail-TransportMailhog / Null-MailerMailpitSMTP / SendGrid

Ungeeignete Ansätze — vermeiden Sie diese:

  • Konfiguration direkt in der Produktionsdatenbank bearbeiten.
  • Verwendung von if ($settings['environment'] === 'prod')-Bedingungen innerhalb von settings.php, um Konfigurationswerte auszutauschen — dies umgeht das Config-System vollständig und führt zu unsichtbaren Laufzeitabweichungen.
  • Separate Git-Branches pro Umgebung mit unterschiedlichen Konfigurationsdateien pflegen.

10. Promotion zwischen Umgebungen

Promotion ist nicht gleich Deployment. Deployment verschiebt Code und Konfiguration in eine Umgebung. Promotion verschiebt ein validiertes Artefakt — etwas, das CI bereits bestanden hat — in die nächste Umgebung der Kette.

In der Praxis bedeutet dies, dass derselbe Git-SHA, der in der Dev-Umgebung validiert wurde, auch die Produktion erreicht. Keine kurzfristigen Commits. Keine Hotfixes, die CI umgehen. Keine Cherry-Picks, die den Config-Check überspringen.

✅ CI validiert SHA
auf Feature-Branch / main
🌐 Dev
Auto-Deploy nach Merge
🌐 Stage
Auto-Deploy von develop
🌐 Prod
Manuelles Gate auf main

Dasselbe validierte Artefakt durchläuft die Umgebungen. CI wird einmal ausgeführt; das Ergebnis propagiert weiter.

Die Philosophie unveränderlicher Builds bedeutet: Was CI validiert hat, wird auch deployt. Wenn ein Hotfix wirklich notwendig ist, durchläuft er einen Fast-Track-Branch mit eigenem CI-Lauf — er umgeht die Validierung nicht.

Verhindern, dass kurzfristige Hotfixes die Config-Prüfungen umgehen: Schützen Sie Ihren main-Branch mit einer verpflichtenden Pipeline-Statusprüfung. Sowohl GitLabs „geschützte Branches“ als auch das Jenkins-Plugin „GitHub Branch Source“ unterstützen dies. Wenn die Pipeline nicht erfolgreich war, kann der Branch nicht in die Produktion deployt werden.

11. Sonderfälle behandeln (Sie werden darauf stoßen)

UUID-Mismatches

Konfigurationsentitäten enthalten UUIDs. Wenn Sie eine frische Site installieren und anschließend versuchen, die Konfiguration aus einer anderen Installation zu importieren, verweigert Drupal dies mit einem UUID-Mismatch-Fehler. Die Lösung besteht darin, entweder immer mit --existing-config zu installieren oder die Site-UUID nach der Installation festzulegen:

Shell
# UUID aus der committeten Konfiguration auslesen und auf die frische Installation anwenden:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yes

Reihenfolge beim Aktivieren/Deaktivieren von Modulen

Beim Aktivieren eines neuen Moduls über Konfiguration respektiert Drupal automatisch die Abhängigkeitsreihenfolge während drush cim. Wenn jedoch ein Installations-Hook eines Moduls Standardkonfiguration erzeugt, die mit der Konfiguration in config/sync kollidiert, müssen Sie diese Standardkonfiguration möglicherweise löschen und erneut importieren. Die Pipeline erkennt dies als Drift.

Inhaltsabhängige Konfiguration

Einige Konfigurationen verweisen auf Inhalte — z. B. ein Block, der auf einen Menüpunkt anhand seiner ID verweist, oder eine View, die nach einem Taxonomiebegriff filtert. Diese Referenzen können sich zwischen Umgebungen unterscheiden, wenn Inhalte unterschiedlich erstellt wurden. Die Lösung besteht darin, diese Inhalte über Migrationen oder Default-Content-Module zu verwalten, nicht über Konfiguration.

Multisite-Besonderheiten

In einer Multisite-Umgebung hat jede Site ihr eigenes Config-Sync-Verzeichnis. Die CI-Pipeline muss die Konfigurationsprüfung für jede Site ausführen, nicht nur für die primäre. Verwenden Sie dazu eine Schleife:

Shellci/multisite-config-check.sh
#!/bin/bash
set -euo pipefail

for SITE_DIR in web/sites/*/; do
  SITE=$(basename "$SITE_DIR")
  [[ "$SITE" == "default" ]] && continue
  [[ "$SITE" == "simpletest" ]] && continue

  echo "--- Prüfe Konfiguration für Site: $SITE ---"
  drush --uri="${SITE}" updb --yes
  drush --uri="${SITE}" cim --yes
  drush --uri="${SITE}" cex --yes

  CONFIG_DIR="config/${SITE}/sync"
  if ! git diff --exit-code "${CONFIG_DIR}"; then
    echo "❌ Config Drift in Site: $SITE"
    exit 1
  fi
done
echo "✅ Alle Sites konsistent."

Drupal-Core-Upgrade mit Konfigurationsänderungen

Core-Updates enthalten manchmal Schemaänderungen, die bestehende Konfiguration betreffen. Führen Sie immer zuerst drush updb vor drush cim aus und danach drush cex, um eventuell schema-normalisierte Änderungen zu erfassen. Committen Sie diese Änderungen als Teil des Core-Upgrade-Branches — lassen Sie sich nicht erst in der Pipeline davon überraschen.

12. Operative Sicherheitsnetze

Schreibgeschützte Konfiguration in Produktion

Erwägen Sie, das Config-Readonly-Modul in der Produktionsumgebung zu aktivieren. Es verhindert jegliche Konfigurationsänderungen über die Admin-UI — eine harte Sperre, die die Regel „keine manuellen Änderungen“ auf Anwendungsebene durchsetzt.

PHPweb/sites/default/settings.env.php
if (getenv('APP_ENV') === 'production') {
  $settings['config_readonly'] = TRUE;
}

Post-Deploy-Verifikation

ShellPost-Deploy-Prüfungen
# Sicherstellen, dass nach dem Deploy keine ausstehenden Konfigurationsänderungen vorhanden sind:
drush config-status 2>&1 | grep -v 'No differences' && { \
  echo "❌ Unerwartete Konfigurationsunterschiede nach dem Deploy"; exit 1; \
} || echo "✅ Konfigurationsstatus sauber"

# Prüfen, ob die Caches aufgewärmt sind:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"

Rollback-Strategie

Rollback ist eine Git-Operation, keine Datenbankoperation. Wenn ein Deployment ein Problem verursacht:

  1. Ermitteln Sie den letzten funktionierenden Commit-SHA.
  2. Starten Sie einen Pipeline-Lauf für diesen SHA.
  3. Die Pipeline deployt das zuvor validierte Artefakt erneut.

Manuelle Datenbankeingriffe, um einen Config-Import rückgängig zu machen, sind niemals die richtige Lösung — sie umgehen sämtliche Sicherheitsprüfungen und erzeugen in der Regel mehr Drift, als sie beheben.

Auditing: Wer hat Konfiguration wann und warum geändert?

Da jede Konfigurationsänderung über Git erfolgt, ist Ihr Audit-Trail Ihre Git-Historie. Verwenden Sie aussagekräftige Commit-Messages und erzwingen Sie diese mit einem commit-msg-Hook oder einem CI-Lint-Schritt:

ShellBeispielhafte Commit-Historie
$ git log --oneline config/sync/views.view.articles.yml

a3f8c12 feat(views): Taxonomie-Filter zur Artikel-View hinzufügen (PROJ-421)
9e1b307 fix(views): Exponierte Sortierung entfernen, die Query-Timeout verursacht (PROJ-389)
4d22a91 chore(config): Export nach Search API Solr 4.3.0 Upgrade

13. Häufige Fehler, die wir nicht mehr machen

FehlerWarum es problematisch istLösung
drush cim manuell auf Staging oder Prod ausführenImportiert einen unbekannten Zustand — möglicherweise inklusive nicht committeter lokaler ÄnderungenNur die Pipeline importiert Konfiguration in gemeinsam genutzten Umgebungen
UI-Änderungen auf Staging zulassenStaging wird zur Quelle der Wahrheit und erzeugt Abweichungen von Gitconfig_readonly auf allen gemeinsam genutzten Umgebungen aktivieren
Darauf vertrauen, dass Entwickler „an den Export denken“Unter Druck werden sie es nicht tunPre-Commit-Hooks warnen; CI-Fehlschläge erzwingen es
Bei Config Drift keinen Fehler auslösenStille Abweichungen sammeln sich an, bis etwas spektakulär brichtDie drush cim → cex → git diff --exit-code-Abfolge ist verpflichtend
Separate Branches pro Umgebung mit unterschiedlicher KonfigurationMerges werden zum Konflikt-Albtraum; keine zentrale Quelle der WahrheitEin Config-Branch, aufgeteilt über config_split
drush updb vor drush cim überspringenSchema-Updates können Importfehler oder stillen Datenverlust verursachenImmer: updb → cim → cex → diff

14. Ergebnisse nach Anwendung dieses Musters

Nach der Einführung dieses Ansatzes über ein Portfolio von Drupal-Sites hinweg — von einer einzelnen kleinen Site bis hin zu einer Agentur mit Dutzenden von Installationen — waren die Ergebnisse durchgehend konsistent:

~80%
Reduktion von konfigurationsbedingten Deployment-Vorfällen
< 1 Tag
Onboarding neuer Entwickler in den Konfigurations-Workflow
0
Manuelle drush cim-Ausführungen in Produktion in 18 Monaten
100%
Konfigurationsänderungen sind einem Git-Commit und MR zuordenbar

Über die Zahlen hinaus ist der kulturelle Wandel am wichtigsten. Config-Diskussionen verlagern sich von Slack in die Git-Historie. Anstatt „Hat jemand die Image Styles auf Staging geändert?“ lautet die Frage nun „Gibt es dafür einen Commit?“ — und entweder gibt es ihn, oder die Änderung hat nicht stattgefunden.

Neue Entwickler müssen nicht mehr die unausgesprochenen Regeln lernen, welche Umgebung gerade kanonisch ist. Die Antwort lautet immer: Git. Die Pipeline setzt dies durch.

15. Abschließende Empfehlungen

  • Beginnen Sie strikt und lockern Sie Regeln nur aus gutem Grund. Es ist einfacher, eine Regel zu lockern, die sich als überflüssig erweist, als eine zu verschärfen, die nie existiert hat.
  • Behandeln Sie jeden Config-Diff als Build-Fehler. Keine Ausnahmen, kein „wir beheben das nach dem Release“.
  • CI ist die Quelle der Wahrheit für das, was deployed wird — nicht der Laptop eines Entwicklers, nicht eine Staging-Umgebung, nicht eine Slack-Entscheidung.
  • Die Abfolge updb → cim → cex → diff ist unantastbar. Wenn Sie einen Schritt überspringen, arbeiten Sie im Blindflug.
  • Verwenden Sie config_split für echte Umgebungsunterschiede. Nutzen Sie keine settings.php-Bedingungen als Abkürzung.
  • Aktivieren Sie config_readonly in allen gemeinsam genutzten Umgebungen. Machen Sie versehentliche UI-Änderungen unmöglich — nicht nur unerwünscht.
  • Archivieren Sie fehlgeschlagene Config-Exporte als CI-Artefakte. Entwickler müssen genau sehen, was vom Sollzustand abweicht — nicht nur, dass es einen Diff gibt.
  • Rollback über Git, niemals über Datenbankeingriffe. Die Pipeline ist in beide Richtungen der sicherste Weg.
Dieser Ansatz skaliert. Egal, ob Sie allein an einer Site arbeiten oder als Agentur fünfzig Drupal-Installationen betreuen — die Regeln bleiben gleich. Die Pipeline kennt weder Teamgröße noch Release-Druck. Sie besteht oder schlägt fehl. Genau diese Konsistenz ist der gesamte Zweck.

Sowohl Jenkins als auch GitLab CI sind gleichermaßen in der Lage, diese Disziplin durchzusetzen. GitLab CI benötigt weniger Infrastruktur und bietet eine bessere native Artefaktunterstützung; Jenkins bietet mehr Flexibilität für komplexe Enterprise-Umgebungen sowie hervorragende Unterstützung für Shared Libraries zur Standardisierung von Pipelines über viele Projekte hinweg. Wählen Sie das Tool, das zu Ihrer Organisation passt — die Prinzipien dieses Artikels gelten unabhängig davon.

Das Ziel war nie, das Konfigurationsmanagement kompliziert zu machen. Das Ziel ist, es langweilig zu machen: ein Routine-Schritt, der immer funktioniert, über den niemand nachdenken muss und der niemals einen Vorfall um 23 Uhr verursacht. Mit einer korrekt implementierten CI-Pipeline ist genau das das Ergebnis.

Technische und architektonische Anfragen
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org