logo

Extra Block Types (EBT) - Nuova esperienza con Layout Builder❗

Extra Block Types (EBT) - tipi di blocchi stilizzati e personalizzabili: Slideshows, Tabs, Cards, Accordion e molti altri. Impostazioni integrate per sfondo, DOM Box, plugin javascript. Vivi oggi il futuro della costruzione dei layout.

Demo moduli EBT Scarica moduli EBT

❗Extra Paragraph Types (EPT) - Nuova esperienza con Paragraphs

Extra Paragraph Types (EPT) - insieme di moduli basati su paragrafi in modo analogo.

Demo moduli EPT Scarica moduli EPT

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

Scorri

Gestione della configurazione Drupal guidata dalla CI utilizzando Jenkins e GitLab CI

16/04/2026, by Ivan

1. Perché la gestione della configurazione guidata dalla CI è importante

Il sistema di configurazione di Drupal è uno dei maggiori punti di forza della piattaforma — e una delle sue fonti più costanti di problemi. La possibilità di esportare e importare ogni elemento della configurazione del sito come file YAML è potente, ma solo se tutti concordano su chi sia responsabile dello spostamento di quei file tra gli ambienti. Nella maggior parte dei team, questo accordo non esiste mai realmente.

I problemi classici sono ben noti a chiunque abbia rilasciato un sito Drupal:

  • Deriva della configurazione — lo staging diverge dalla produzione, la produzione diverge dall’ambiente locale e nessuno è sicuro di quale ambiente sia quello canonico.
  • "Funziona in staging ma non in produzione" — perché qualcuno ha aggiornato una vista o un formatter di campo in staging e non l’ha mai esportato.
  • Esecuzione manuale di drush cim che rompe i contenuti — un'importazione frettolosa alle 23:00 che ha eliminato un campo di tipo di contenuto ancora referenziato da nodi attivi.

La causa principale di ognuno di questi scenari è la stessa: un essere umano decide quando e se la configurazione viene promossa. Gli esseri umani dimenticano. Saltano passaggi sotto pressione. Prendono decisioni che si rivelano errate.

La CI non dimentica. Una pipeline o passa o fallisce. Non ha uno standup a cui partecipare. Non sa che il rilascio è tra venti minuti. Questo determinismo è esattamente ciò di cui la gestione della configurazione ha bisogno.

La promessa che questo articolo mantiene:

  • Ogni modifica alla configurazione viene salvata in Git prima di toccare qualsiasi ambiente condiviso.
  • La pipeline, non uno sviluppatore, è responsabile della validazione e dell'importazione della configurazione.
  • La promozione tra ambienti non richiede alcun passaggio manuale.
  • La deriva della configurazione è un errore di build, non un messaggio su Slack.
Presupposti: Stai eseguendo Drupal 10 o 11, utilizzando un flusso di lavoro basato su Git con branch di funzionalità, e distribuisci su almeno due ambienti condivisi (ad es. staging e produzione). Il tuo team utilizza Jenkins o GitLab CI — o entrambi.

2. Principi fondamentali che abbiamo imparato da progetti reali

La configurazione è codice

Se cambia il comportamento del sito, appartiene a Git. Punto. Una vista, un tipo di contenuto, un'impostazione delle prestazioni, uno stile di immagine — tutto è codice. Tratta i file di configurazione con la stessa disciplina dei file PHP: revisionali, versionali, non modificarli mai direttamente in un ambiente condiviso.

Nessun drush cim manuale negli ambienti condivisi

La pipeline importa la configurazione. Gli sviluppatori no. Questa regola può sembrare estrema finché non si verifica la prima volta che qualcuno esegue drush cim in produzione con modifiche locali non salvate ancora presenti nella directory di lavoro.

Le pipeline devono fallire rapidamente in caso di deriva della configurazione

Importare la configurazione e poi esportarla immediatamente non dovrebbe produrre alcuna differenza. Se accade, la build fallisce. Questa singola regola intercetta più bug di qualsiasi altro controllo che abbiamo aggiunto.

Uno sviluppatore senior stava eseguendo il debug di una pagina di ricerca lenta in produzione. Ha modificato le impostazioni dell'indice Search API tramite l’interfaccia utente, ha confermato che le prestazioni miglioravano e ha chiuso il ticket. Due settimane dopo, un rilascio ha importato la configurazione da Git — sovrascrivendo le sue modifiche fatte tramite UI — e le prestazioni della ricerca sono crollate. Nessuno ha collegato i puntini per tre giorni. Dopo quell'incidente, abbiamo reso esplicita la regola: ogni modifica tramite UI che non viene immediatamente esportata e salvata è considerata persa.
Su un sito ricco di media, core.extension.yml è stato escluso dalle esportazioni perché "i moduli sono comunque gestiti da Composer". Tre mesi dopo, una hotfix ha riattivato un modulo che era stato deliberatamente disabilitato in produzione. La hotfix era corretta — ma una successiva importazione della configurazione ha disabilitato nuovamente il modulo in modo silenzioso. Questo incidente è il motivo per cui ora chiamiamo core.extension.yml il file più pericoloso che si possa ignorare.

3. Architettura di alto livello

👨‍💻 Sviluppatore
drush cex
📁 Repository Git
config/sync
⚙ Pipeline CI
validazione + importazione
🌐 Dev
🌐 Stage
🌐 Prod

La configurazione scorre in una sola direzione: dall'esportazione locale dello sviluppatore, attraverso Git, tramite la pipeline CI, e all'interno di ciascun ambiente.

L'intuizione chiave è la separazione delle responsabilità:

AttoreResponsabilitàMai responsabile di
SviluppatoreEsportare la configurazione, effettuare il commit su Git, aprire MR/PRImportare la configurazione su qualsiasi ambiente condiviso
Pipeline CIValidare, importare, verificare, promuovereGenerare o modificare file di configurazione
AmbienteEseguire il sitoGenerare, archiviare o esportare la configurazione

Gli ambienti sono consumatori di configurazione, mai produttori. Nel momento in cui un ambiente diventa una fonte di verità per la configurazione, la deriva diventa inevitabile.

4. Una struttura di repository che scala

Struttura delle directory root del progetto
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│   ├── drupal-config-check.sh   # Script di validazione riutilizzabile
│   ├── drupal-deploy.sh
│   └── drupal-install.sh
│
├── config/
│   ├── sync/                        # La configurazione vive qui — salvata su Git
│   │   ├── core.extension.yml
│   │   ├── system.site.yml
│   │   └── ...
│   └── splits/                      # Override config_split per ambiente
│       ├── development/
│       ├── staging/
│       └── production/
│
└── web/
    ├── sites/
    │   └── default/
    │       ├── settings.php          # Salvato su Git, senza segreti
    │       ├── settings.local.php    # Escluso da Git, override locali
    │       └── settings.env.php      # Caricato dalla CI tramite variabili d'ambiente
    └── ...

Cosa si trova in config/sync

Tutti i file YAML di configurazione generati da drush cex. Questa directory è l'unica fonte di verità per la configurazione del sito. Viene salvata, revisionata e distribuita come qualsiasi altro codice.

Cosa non deve mai entrare nella configurazione

  • Hostname specifici per ambiente, chiavi API o credenziali — utilizzare variabili d'ambiente e settings.env.php.
  • Segreti di qualsiasi tipo — iniettarli tramite il secret store della CI (Jenkins Credentials o GitLab CI Variables).
  • Contenuto — non utilizzare il modulo Default Content come soluzione temporanea per ciò che dovrebbe essere configurazione.
PHP web/sites/default/settings.php
// settings.php — salvato su Git, indipendente dall'ambiente
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';

// Carica i valori specifici dell'ambiente iniettati dalla CI o dall'host.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
  include $app_root . '/' . $site_path . '/settings.env.php';
}

// Carica eventuali override locali opzionali (esclusi da Git).
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
// Generato dalla CI a partire dalle variabili d'ambiente — mai salvato su Git.
$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');

// Indica a config_split in quale ambiente ci troviamo.
$config['config_split.config_split.production']['status'] =
  (getenv('APP_ENV') === 'production');

5. Workflow di configurazione Drupal

5.1 Sviluppo locale

Il workflow locale è l'unico contesto in cui il ciclo di feedback tra le modifiche tramite UI e l'esportazione della configurazione deve essere rapido e abituale. Ogni sviluppatore del progetto segue le stesse regole:

  • Apportare modifiche tramite UI o codice in locale.
  • Eseguire immediatamente drush cex — non alla fine della giornata.
  • Revisionare le differenze con git diff config/sync.
  • Effettuare il commit delle modifiche oppure scartarle con git checkout -- config/sync.
  • Non esiste uno stato intermedio.
Shell Workflow di sviluppo locale
# Dopo aver apportato modifiche alla configurazione tramite la UI di Drupal o install hook:
drush cex --yes

# Revisionare cosa è cambiato — trattalo come qualsiasi modifica al codice:
git diff config/sync

# Effettuare lo stage e il commit insieme al codice della funzionalità:
git add config/sync
git commit -m "feat(search): aggiungi configurazione indice Search API fulltext"

# Oppure scartare se la modifica era esplorativa e non pronta:
git checkout -- config/sync

Applichiamo questa regola con un leggero hook Git pre-commit che avvisa (ma non blocca) quando file PHP o template vengono messi in stage senza una corrispondente modifica in config/sync:

Shell .git/hooks/pre-commit
#!/bin/bash
# Avvisa se il codice del modulo/tema è cambiato ma config/sync no.
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 "⚠  Attenzione: file PHP/modulo in stage ma nessuna modifica rilevata in config/sync."
  echo "   Hai dimenticato di eseguire: drush cex ?"
  echo "   Continuo comunque — ma ricontrolla."
fi
exit 0

5.2 Feature Branch

La configurazione vive insieme al codice che la richiede nello stesso branch e nella stessa pull/merge request. Una funzionalità che aggiunge un nuovo tipo di contenuto dovrebbe includere sia l'install hook PHP (se presente) sia i file YAML per quel tipo di contenuto nello stesso commit.

Individuare rapidamente dipendenze rotte: Se il branch A aggiunge un nuovo campo e il branch B modifica la visualizzazione di quello stesso campo, unire B prima di A produrrà un errore di importazione della configurazione. Questo è esattamente il risultato corretto — vuoi che la pipeline lo intercetti nel branch, non in produzione.

6. Responsabilità della CI: cosa deve far rispettare la Pipeline

Ogni pipeline che interagisce con un ambiente Drupal condiviso deve eseguire questi passaggi in ordine:

  1. Installare o ripristinare il database — un’installazione pulita o uno snapshot sanificato della produzione.
  2. Eseguire gli aggiornamenti del databasedrush updb.
  3. Importare la configurazionedrush cim --yes.
  4. Riesportare la configurazionedrush cex --yes.
  5. Verificare l’assenza di differenze — se qualsiasi file YAML è cambiato, la build deve fallire.

I passaggi 4 e 5 insieme sono i più importanti. Importare e poi riesportare immediatamente deve produrre una diff vuota. Se non accade, una di queste condizioni è vera:

  • Un modulo genera configurazione durante l'importazione (spesso un bug nel modulo).
  • L’UUID di un’entità di configurazione non corrisponde a quello nel database.
  • Uno schema di configurazione è incompleto, causando una normalizzazione dei valori da parte di Drupal diversa rispetto all’esportazione.
  • Uno sviluppatore ha modificato manualmente i file di configurazione introducendo incoerenze.
Shell ci/drupal-config-check.sh
#!/bin/bash
set -euo pipefail

echo "=== Esecuzione aggiornamenti database Drupal ==="
drush updb --yes

echo "=== Importazione configurazione da config/sync ==="
drush cim --yes

echo "=== Riesportazione per verificare l'assenza di modifiche pendenti ==="
drush cex --yes

echo "=== Verifica della deriva della configurazione ==="
if ! git diff --exit-code config/sync; then
  echo ""
  echo "❌ ERRORE: Rilevata deriva della configurazione!"
  echo "   La configurazione in Git non corrisponde a quanto prodotto da Drupal dopo importazione+esportazione."
  echo "   Diff mostrata sopra. Correggere localmente con 'drush cex' ed effettuare il commit."
  exit 1
fi

echo "✅ La configurazione è pulita — nessuna deriva rilevata."
Perché riesportare dopo l’importazione è imprescindibile: Senza il passaggio di riesportazione, sai solo che l'importazione è riuscita. Non sai se la configurazione importata corrisponde a ciò che Drupal ha effettivamente memorizzato internamente. Solo la riesportazione e il confronto delle differenze colmano questa lacuna.
 

7. Implementazione Jenkins (collaudata sul campo)

7.1 Struttura del Jenkinsfile

Utilizziamo esclusivamente pipeline dichiarative. Le pipeline scriptate offrono maggiore flessibilità, ma le pipeline dichiarative sono più facili da leggere, da verificare con jenkins-cli declarative-linter e da comprendere per i nuovi membri del team.


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') // Segreto Jenkins
  }

  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 {
        // Genera settings.env.php da credenziali/variabili Jenkins
        sh '''
          cat > web/sites/default/settings.env.php <<EOF
<?php
$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: "Validazione configurazione fallita. Vedi: ${BUILD_URL}",
        recipientProviders: [[$class: 'DevelopersRecipientProvider']]
      )
    }
  }
}

7.2 Considerazioni specifiche per Jenkins

Shared Libraries

Quando si gestiscono più di due o tre siti Drupal, estrarre la logica della pipeline Drupal in una Jenkins Shared Library. Il Jenkinsfile di ciascun progetto diventa quindi un wrapper minimale:

Groovy Jenkinsfile (wrapper sottile che utilizza una shared library)
@Library('drupal-pipeline-lib@v2') _

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

Pulizia del Workspace

Chiamare sempre cleanWs() nel blocco post { always }. Gli agent Jenkins accumulano stato dalle build precedenti — un settings.env.php o la directory vendor da una build precedente possono influenzare silenziosamente quella corrente. Siate drastici: pulite il workspace.

Non riutilizzare mai un database tra una pipeline e l'altra a meno che non si stiano testando esplicitamente percorsi di aggiornamento. Un database residuo da una build precedente può mascherare mismatch di UUID, migrazioni mancanti e problemi nell'ordine di installazione dei moduli.

8. Implementazione GitLab CI

8.1 Struttura di .gitlab-ci.yml

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 Vantaggi di GitLab CI

GitLab CI dispone di diverse funzionalità che rendono questo workflow particolarmente pulito:

  • Artifact di prima classe — le esportazioni di configurazione fallite vengono automaticamente salvate e consultabili nell'interfaccia MR senza configurazioni aggiuntive.
  • Caching nativo basato su composer.lock — le installazioni Composer sono rapide dopo la prima esecuzione.
  • Visibilità della pipeline nelle Merge Request — gli sviluppatori vedono lo stato della validazione della configurazione direttamente nella MR prima del merge.
  • Blocco Services — MySQL viene avviato come sidecar senza overhead infrastrutturale.
  • Ambienti + gate manuali — la regola when: manual nel deploy in produzione consente la promozione con un clic mantenendo un controllo umano, senza richiedere un workflow di approvazione separato.
Shell ci/drupal-deploy.sh
#!/bin/bash
set -euo pipefail

TARGET_ENV="${1:-staging}"
echo "=== Deploy su: ${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 "=== Esecuzione comandi post-deploy su ${TARGET_ENV} ==="
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 "❌ Deriva configurazione sul target!"; exit 1)
  drush cr
  echo "✅ Deploy completato."
REMOTE

9. Configurazione specifica per ambiente senza scorciatoie

Non tutti i valori di configurazione devono essere gli stessi in tutti gli ambienti. Backend di ricerca, livello di verbosità dei log, livelli di caching e endpoint di API di terze parti differiscono legittimamente. La domanda è come gestire questa differenza senza compromettere l'integrità della tua pipeline di configurazione.

Gli strumenti corretti: config_split e config_ignore

config_split consente di definire insiemi di configurazione attivi solo in ambienti specifici. Ogni split vive nella propria directory ed è attivato tramite settings.php o settings.env.php in base a una variabile di ambiente.

YAML config/sync/config_split.config_split.development.yml
langcode: en
status: true
id: development
label: Development
description: 'Config attiva solo negli ambienti locale/dev'
folder: '../config/splits/development'
module:
  devel: 0
  kint: 0
  dblog: 0
theme: {  }
blacklist: {  }
graylist: {  }
PHP web/sites/default/settings.env.php (attivazione ambiente)
$app_env = getenv('APP_ENV') ?: 'production';

// Attiva il config_split corretto in base all'ambiente.
$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');

Esempi reali di split

Elemento di configurazioneSplit DevSplit StageSplit Prod
Backend Search APIBackend databaseSolr (piccolo)Solr (cluster produzione)
Logging erroridblog, verbososyslogsyslog + APM esterno
Moduli di performanceDisabilitatiAbilitatiAbilitati + configurazione CDN
Trasporto emailMailhog / null mailerMailpitSMTP / SendGrid

Approcci impropri — da evitare:

  • Modificare la configurazione direttamente sul database di produzione.
  • Usare condizioni come if ($settings['environment'] === 'prod') all'interno di settings.php per sostituire valori di configurazione — questo aggira completamente il sistema di configurazione e crea divergenze runtime invisibili.
  • Mantenere branch Git separati per ambiente con file di configurazione differenti.

10. Promozione tra ambienti

La promozione non è il deployment. Il deployment sposta codice e configurazione in un ambiente. La promozione sposta un artifact validato — qualcosa che ha già superato la CI — verso il prossimo ambiente nella catena.

In pratica, questo significa che lo stesso Git SHA validato su dev è lo SHA che raggiunge la produzione. Nessun commit dell’ultimo minuto. Nessuna hotfix che salta la CI. Nessun cherry-pick che bypassa il controllo della configurazione.

✅ CI valida lo SHA
su feature branch / main
🌐 Dev
auto-deploy dopo merge
🌐 Stage
auto-deploy da develop
🌐 Prod
gate manuale su main

Lo stesso artifact validato attraversa gli ambienti. La CI viene eseguita una sola volta; il risultato si propaga.

La filosofia delle build immutabili significa: ciò che la CI ha validato è ciò che viene distribuito. Se una hotfix è veramente necessaria, passa attraverso un branch a corsia preferenziale con la propria esecuzione CI — non aggira la validazione.

Prevenire che hotfix dell’ultimo minuto saltino i controlli di configurazione: Proteggi il tuo branch main con un controllo obbligatorio dello stato della pipeline. Le "protected branches" di GitLab e il plugin "GitHub Branch Source" di Jenkins supportano entrambi questa funzionalità. Se la pipeline non è passata, il branch non può essere distribuito in produzione.

11. Gestire i casi limite (li incontrerai)

Mismatch UUID

Le entità di configurazione hanno UUID. Se installi un sito ex novo e poi provi a importare la configurazione da un’installazione diversa, Drupal rifiuterà l’operazione con un errore di mismatch UUID. La soluzione è installare sempre con --existing-config oppure impostare l’UUID del sito dopo l’installazione:

Shell
# Legge l'UUID dalla configurazione salvata e lo applica alla nuova installazione:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yes

Ordine di abilitazione/disabilitazione dei moduli

Quando si abilita un nuovo modulo tramite configurazione, Drupal rispetta automaticamente l'ordine delle dipendenze durante drush cim. Tuttavia, se l’install hook di un modulo genera una configurazione predefinita che è in conflitto con quanto presente in config/sync, potrebbe essere necessario eliminare quella configurazione predefinita e reimportare. La pipeline lo rileverà come deriva.

Configurazione dipendente dal contenuto

Alcune configurazioni fanno riferimento al contenuto — ad esempio, un blocco che fa riferimento a una voce di menu per ID, o una vista che filtra per un termine di tassonomia. Questi riferimenti possono differire tra ambienti se il contenuto è stato creato in modo diverso. La soluzione è gestire quel contenuto tramite migrazioni o moduli di default content, non tramite configurazione.

Particolarità Multisite

In una configurazione multisite, ogni sito ha la propria directory di sincronizzazione della configurazione. La pipeline CI deve eseguire il controllo della configurazione per ogni sito, non solo per quello principale. Utilizza un ciclo:

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 "--- Controllo configurazione per il sito: $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 "❌ Deriva configurazione nel sito: $SITE"
    exit 1
  fi
done
echo "✅ Tutti i siti sono corretti."

Aggiornamento di Drupal Core con modifiche alla configurazione

Gli aggiornamenti del core a volte includono modifiche allo schema che influenzano la configurazione esistente. Esegui sempre drush updb prima di drush cim, ed esegui poi drush cex per acquisire eventuali modifiche normalizzate dallo schema. Effettua il commit di queste modifiche come parte del branch di aggiornamento del core — non lasciarti sorprendere dalla pipeline.

12. Reti di sicurezza operative

Configurazione in sola lettura in produzione

Valuta di abilitare il modulo Config Readonly in produzione. Impedisce qualsiasi modifica della configurazione tramite l’interfaccia admin — un blocco rigido che rafforza la regola "nessuna modifica manuale" a livello applicativo.

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

Verifiche post-deploy

ShellVerifiche post-deploy
# Verifica che non rimangano modifiche di configurazione dopo il deploy:
drush config-status 2>&1 | grep -v 'No differences' && { \
  echo "❌ Differenze di configurazione inattese dopo il deploy"; exit 1; \
} || echo "✅ Stato configurazione corretto"

# Verifica che le cache siano attive:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"

Strategia di rollback

Il rollback è un'operazione Git, non un'operazione sul database. Se un deployment causa un problema:

  1. Identifica l’ultimo commit SHA funzionante.
  2. Avvia una pipeline su quello SHA.
  3. La pipeline ridistribuisce l'artifact precedentemente validato.

La chirurgia manuale sul database per annullare un’importazione di configurazione non è mai la soluzione — aggira tutti i controlli di sicurezza e in genere crea più deriva di quanta ne risolva.

Audit: chi ha cambiato la configurazione, quando e perché

Poiché ogni modifica di configurazione passa attraverso Git, la tua traccia di audit è la cronologia Git. Utilizza messaggi di commit significativi e applicali con un hook commit-msg o un passaggio di lint nella CI:

ShellEsempio cronologia commit
$ git log --oneline config/sync/views.view.articles.yml

a3f8c12 feat(views): aggiungi filtro tassonomia alla vista articoli (PROJ-421)
9e1b307 fix(views): rimuovi ordinamento esposto che causava timeout della query (PROJ-389)
4d22a91 chore(config): esportazione dopo aggiornamento Search API Solr 4.3.0

13. Errori comuni che non commettiamo più

ErrorePerché è dannosoLa soluzione
Eseguire drush cim manualmente su staging o produzioneImporta uno stato sconosciuto — che potrebbe includere modifiche locali non salvateSolo la pipeline importa la configurazione su ambienti condivisi
Consentire modifiche via UI su stagingLo staging diventa una fonte di verità, creando divergenza da GitAbilitare config_readonly su tutti gli ambienti condivisi
Fidarsi che gli sviluppatori "si ricordino di esportare"Non lo faranno, sotto pressioneGli hook pre-commit avvisano; il fallimento CI impone
Non fallire in caso di deriva della configurazioneLa divergenza silenziosa si accumula finché qualcosa non si rompe in modo spettacolareLa sequenza drush cim → cex → git diff --exit-code è obbligatoria
Branch separati per ambiente con configurazioni diverseIl merge diventa un incubo di conflitti; nessuna singola fonte di veritàUn solo branch di configurazione, suddiviso tramite config_split
Saltare drush updb prima di drush cimGli aggiornamenti di schema possono causare errori di importazione o perdita silenziosa di datiSempre: updb → cim → cex → diff

14. Risultati dopo l’applicazione di questo approccio

Dopo aver implementato questo approccio su un portafoglio di siti Drupal — da un singolo piccolo sito a un’agenzia che gestisce decine di installazioni — i risultati sono stati costanti:

~80%
Riduzione degli incidenti di deployment legati alla configurazione
< 1 giorno
Onboarding di un nuovo sviluppatore al workflow di configurazione
0
Esecuzioni manuali di drush cim in produzione in 18 mesi
100%
Modifiche di configurazione tracciabili a un commit Git e MR

Oltre ai numeri, il cambiamento culturale è ciò che conta di più. Le discussioni sulla configurazione passano da Slack alla cronologia di Git. Invece di "qualcuno ha modificato gli image styles su staging?", la domanda diventa "esiste un commit per questo?" — e c’è sempre, oppure la modifica non è mai avvenuta.

I nuovi sviluppatori non devono più imparare le regole non scritte su "quale ambiente è canonico in questo momento". La risposta è sempre Git. La pipeline lo impone.

15. Raccomandazioni finali

  • Inizia con regole rigide, rilassa solo se necessario. È più facile allentare una regola inutile che rafforzarne una che non è mai esistita.
  • Tratta ogni diff di configurazione come un fallimento della build. Nessuna eccezione.
  • La CI è la fonte di verità per ciò che viene distribuito — non il laptop di uno sviluppatore, non un ambiente di staging, non una decisione su Slack.
  • La sequenza updb → cim → cex → diff è inviolabile.
  • Usa config_split per reali differenze tra ambienti. Non usare condizioni in settings.php come scorciatoia.
  • Abilita config_readonly su tutti gli ambienti condivisi.
  • Archivia le esportazioni di configurazione fallite come artifact CI.
  • Rollback tramite Git, mai tramite interventi sul database.
Questo approccio è scalabile. Che tu sia uno sviluppatore singolo o un’agenzia che gestisce cinquanta installazioni Drupal, le regole sono le stesse.

Sia Jenkins che GitLab CI sono perfettamente in grado di applicare questa disciplina. GitLab CI richiede meno infrastruttura e ha un miglior supporto nativo per gli artifact; Jenkins offre maggiore flessibilità per ambienti enterprise complessi e un eccellente supporto per le shared libraries per standardizzare le pipeline su molti progetti.

L’obiettivo non è mai stato rendere complessa la gestione della configurazione. L’obiettivo è renderla noiosa: un passaggio di routine che funziona sempre, che nessuno deve pensare, che non causa mai incidenti alle 23:00.

Richieste tecniche e architetturali
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
Richieste di progetto
projects@drupalbook.org