CI‑gesteuertes Drupal‑Konfigurationsmanagement mit Jenkins und GitLab CI
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 cimzerstö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.
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.
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
drush cex
config/sync
Validierung + Import
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:
| Akteur | Verantwortung | Niemals verantwortlich für |
|---|---|---|
| Entwickler | Konfiguration exportieren, in Git committen, MR/PR eröffnen | Importieren von Konfiguration in einer gemeinsam genutzten Umgebung |
| CI-Pipeline | Validieren, importieren, verifizieren, promoten | Erzeugen oder Bearbeiten von Konfigurationsdateien |
| Umgebung | Ausführen der Site | Erzeugen, 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
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.
// 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';
}// 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 cexausführen — nicht erst am Ende des Tages. - Die Änderungen mit
git diff config/syncüberprüfen. - Änderungen entweder committen oder mit
git checkout -- config/syncverwerfen. - Einen Zwischenstatus gibt es nicht.
# 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/syncDies 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:
#!/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 05.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.
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:
- Datenbank installieren oder wiederherstellen — entweder eine saubere Installation oder einen bereinigten Produktions-Snapshot.
- Datenbank-Updates ausführen —
drush updb. - Konfiguration importieren —
drush cim --yes. - Konfiguration erneut exportieren —
drush cex --yes. - 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.
#!/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."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:
@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.
8. GitLab-CI-Implementierung
8.1 .gitlab-ci.yml-Struktur
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 production8.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.
#!/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."
REMOTE9. 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.
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: { }$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
| Konfigurationselement | Dev-Split | Stage-Split | Prod-Split |
|---|---|---|---|
| Search API Backend | Datenbank-Backend | Solr (klein) | Solr (Produktions-Cluster) |
| Fehlerprotokollierung | dblog, ausführlich | syslog | syslog + externes APM |
| Performance-Module | Deaktiviert | Aktiviert | Aktiviert + CDN-Konfiguration |
| Mail-Transport | Mailhog / Null-Mailer | Mailpit | SMTP / SendGrid |
Ungeeignete Ansätze — vermeiden Sie diese:
- Konfiguration direkt in der Produktionsdatenbank bearbeiten.
- Verwendung von
if ($settings['environment'] === 'prod')-Bedingungen innerhalb vonsettings.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.
auf Feature-Branch / main
Auto-Deploy nach Merge
Auto-Deploy von develop
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.
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:
# 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 --yesReihenfolge 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:
#!/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.
if (getenv('APP_ENV') === 'production') {
$settings['config_readonly'] = TRUE;
}Post-Deploy-Verifikation
# 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:
- Ermitteln Sie den letzten funktionierenden Commit-SHA.
- Starten Sie einen Pipeline-Lauf für diesen SHA.
- 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:
$ 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 Upgrade13. Häufige Fehler, die wir nicht mehr machen
| Fehler | Warum es problematisch ist | Lösung |
|---|---|---|
drush cim manuell auf Staging oder Prod ausführen | Importiert einen unbekannten Zustand — möglicherweise inklusive nicht committeter lokaler Änderungen | Nur die Pipeline importiert Konfiguration in gemeinsam genutzten Umgebungen |
| UI-Änderungen auf Staging zulassen | Staging wird zur Quelle der Wahrheit und erzeugt Abweichungen von Git | config_readonly auf allen gemeinsam genutzten Umgebungen aktivieren |
| Darauf vertrauen, dass Entwickler „an den Export denken“ | Unter Druck werden sie es nicht tun | Pre-Commit-Hooks warnen; CI-Fehlschläge erzwingen es |
| Bei Config Drift keinen Fehler auslösen | Stille Abweichungen sammeln sich an, bis etwas spektakulär bricht | Die drush cim → cex → git diff --exit-code-Abfolge ist verpflichtend |
| Separate Branches pro Umgebung mit unterschiedlicher Konfiguration | Merges werden zum Konflikt-Albtraum; keine zentrale Quelle der Wahrheit | Ein Config-Branch, aufgeteilt über config_split |
drush updb vor drush cim überspringen | Schema-Updates können Importfehler oder stillen Datenverlust verursachen | Immer: 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:
drush cim-Ausführungen in Produktion in 18 MonatenÜ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 → diffist unantastbar. Wenn Sie einen Schritt überspringen, arbeiten Sie im Blindflug. - Verwenden Sie
config_splitfür echte Umgebungsunterschiede. Nutzen Sie keinesettings.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.
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.
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
projects@drupalbook.org