Gestão de configuração do Drupal baseada em CI com Jenkins e GitLab CI
1. Por que a gestão de configuração baseada em CI importa
O sistema de configuração do Drupal é um dos pontos mais fortes da plataforma e, ao mesmo tempo, uma das fontes mais confiáveis de dor de cabeça. A capacidade de exportar e importar cada item de configuração do site na forma de arquivos YAML é incrivelmente poderosa — mas só se todos concordarem sobre quem exatamente é responsável por mover esses arquivos entre os ambientes. Na maioria das equipes, esse acordo simplesmente não existe.
Os problemas clássicos são bem conhecidos de qualquer um que já tenha operado um site Drupal:
- Deriva de configuração (config drift) — o staging diverge do production, o production diverge do ambiente local, e ninguém tem certeza de qual deles é a referência.
- «Funciona no staging, mas não no prod» — porque alguém atualizou uma view ou um formatador de campo no staging e nunca exportou as mudanças.
- O
drush cimmanual que quebra o conteúdo — uma importação apressada às 23h que removeu um campo de tipo de conteúdo ainda usado por nós ao vivo.
A causa raiz de todos esses cenários é a mesma: um humano decide quando, e se, promover a configuração. As pessoas esquecem. Pulam etapas sob pressão. Tomam decisões que se mostram erradas.
O CI não esquece. O pipeline ou passa ou falha. Ele não tem uma reunião para correr. Não sabe que há um release em vinte minutos. É exatamente esse determinismo que a gestão de configuração precisa.
As promessas que este artigo cumpre:
- Toda mudança de configuração é commitada no Git antes de chegar a qualquer ambiente compartilhado.
- A validação e a importação da configuração são responsabilidade do pipeline, não do desenvolvedor.
- A promoção entre ambientes não exige nenhuma etapa manual.
- A deriva de configuração é um erro de build, e não uma mensagem no Slack.
2. Princípios básicos que aprendemos em projetos reais
Configuração é código
Se algo muda o comportamento do site, seu lugar é no Git. Ponto final. Uma view, um tipo de conteúdo, um ajuste de desempenho, um estilo de imagem — tudo isso é código. Trate os arquivos de configuração com a mesma disciplina dos arquivos PHP: faça revisão, versione, nunca os edite diretamente em um ambiente compartilhado.
Nenhum drush cim manual em ambientes compartilhados
Quem importa a configuração é o pipeline. Não os desenvolvedores. Essa regra soa radical até você presenciar, pela primeira vez, alguém executar drush cim em produção com mudanças locais não commitadas no diretório de trabalho.
Os pipelines devem falhar diante de qualquer deriva de configuração
Importar a configuração e, em seguida, exportá-la imediatamente não deve produzir nenhuma diferença. Se houver diferença — o build falha. Essa única regra pega mais bugs do que todas as outras verificações que adicionamos.
core.extension.yml foi excluído das exportações porque «os módulos são gerenciados pelo Composer mesmo». Três meses depois, um hotfix reativou um módulo que havia sido intencionalmente desativado em produção. O hotfix estava correto — mas a importação de configuração seguinte silenciosamente desativou o módulo de novo. Foi exatamente após esse caso que passamos a chamar o core.extension.yml de o arquivo mais perigoso que se pode ignorar.3. Arquitetura de alto nível
drush cex
config/sync
validação + importação
O fluxo de configuração se move em uma única direção: da exportação local do desenvolvedor, passando pelo Git, pelo pipeline de CI e, então, para cada ambiente.
A ideia central é a separação de responsabilidades:
| Participante | Responsabilidade | Nunca é responsável por |
|---|---|---|
| Desenvolvedor | Exportar a configuração, commitar no Git, criar MR/PR | Importar a configuração em qualquer ambiente compartilhado |
| Pipeline de CI | Validar, importar, verificar, promover | Gerar ou editar arquivos de configuração |
| Ambiente | Executar o site | Gerar, armazenar ou exportar configuração |
Os ambientes são consumidores de configuração, e não suas fontes. No momento em que um ambiente se torna a fonte da verdade para a configuração, a deriva se torna inevitável.
4. Estrutura de repositório escalável
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│ ├── drupal-config-check.sh # Script de validação compartilhado
│ ├── drupal-deploy.sh
│ └── drupal-install.sh
│
├── config/
│ ├── sync/ # A configuração fica aqui — commitada no Git
│ │ ├── core.extension.yml
│ │ ├── system.site.yml
│ │ └── ...
│ └── splits/ # Sobrescritas de config_split por ambiente
│ ├── development/
│ ├── staging/
│ └── production/
│
└── web/
├── sites/
│ └── default/
│ ├── settings.php # Commitado, sem segredos
│ ├── settings.local.php # Ignorado pelo Git, sobrescritas locais
│ └── settings.env.php # Carregado pelo CI a partir de variáveis de ambiente
└── ...
O que fica em config/sync
Todos os arquivos YAML de configuração gerados pelo comando drush cex. Esse diretório é a única fonte da verdade para a configuração do site. Ele é commitado, revisado e implantado como qualquer outro código.
O que nunca deve entrar na configuração
- Nomes de host, chaves de API ou credenciais específicas de ambiente — use variáveis de ambiente e
settings.env.php. - Segredos de qualquer tipo — injete-os por meio do cofre de segredos do CI (Jenkins Credentials ou GitLab CI Variables).
- Conteúdo — não use o módulo Default Content como gambiarra onde deveria haver configuração.
// settings.php — commitado no Git, independente de ambiente
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';
// Carrega valores específicos do ambiente, injetados pelo CI ou pela hospedagem.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
include $app_root . '/' . $site_path . '/settings.env.php';
}
// Carrega sobrescritas locais opcionais (ignoradas pelo Git).
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}
// Gerado pelo CI a partir de variáveis de ambiente — nunca é commitado.
$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');
// Informa ao config_split em qual ambiente estamos.
$config['config_split.config_split.production']['status'] =
(getenv('APP_ENV') === 'production');
5. Fluxo de trabalho de configuração do Drupal
5.1 Desenvolvimento local
O fluxo local é o único lugar onde o ciclo de feedback entre as mudanças na UI e a exportação da configuração deve ser rápido e habitual. Cada desenvolvedor do projeto segue as mesmas regras:
- Fazer mudanças na UI ou no código localmente.
- Executar
drush ceximediatamente — não no fim do dia. - Revisar o diff com
git diff config/sync. - Commitar as mudanças ou descartá-las com
git checkout -- config/sync. - Não existe um estado intermediário.
# Depois de fazer mudanças de configuração pela UI do Drupal ou por install hooks:
drush cex --yes
# Revise as mudanças — trate isso como a revisão de qualquer código:
git diff config/sync
# Adicione ao staging e commite junto com o código da feature:
git add config/sync
git commit -m "feat(search): add fulltext search API index config"
# Ou descarte, se a mudança foi experimental e ainda não está pronta:
git checkout -- config/syncReforçamos isso ainda com um Git pre-commit hook leve que avisa (mas não bloqueia) quando arquivos PHP ou de template são adicionados ao staging sem as mudanças correspondentes em config/sync:
#!/bin/bash
# Avisar se o código de módulo/tema mudou, mas config/sync não.
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 "⚠ Warning: PHP/module files staged but no config/sync changes detected."
echo " Did you forget to run: drush cex ?"
echo " Proceeding anyway — but double-check."
fi
exit 05.2 Feature branches
A configuração vive ao lado do código que depende dela — na mesma branch e no mesmo pull/merge request. Uma feature que adiciona um novo tipo de conteúdo deve incluir tanto o PHP install hook (se houver) quanto os arquivos YAML desse tipo de conteúdo no mesmo commit.
6. Responsabilidades do CI: o que o pipeline deve garantir
Todo pipeline que trabalha com um ambiente Drupal compartilhado deve executar estas etapas na ordem:
- Instalar ou restaurar o banco de dados — instalação limpa ou snapshot anonimizado de produção.
- Executar as atualizações do banco de dados —
drush updb. - Importar a configuração —
drush cim --yes. - Reexportar a configuração —
drush cex --yes. - Garantir a ausência de diff — se qualquer arquivo YAML mudou, o build deve falhar.
As etapas 4 e 5 são as mais importantes. Importar e, em seguida, reexportar imediatamente não deve produzir nenhuma diferença. Se houver diferenças, uma das seguintes coisas é verdadeira:
- Um módulo gera configuração durante a importação (muitas vezes é um bug no módulo).
- O UUID da entidade de configuração não coincide com o que está no banco de dados.
- O schema da configuração está incompleto, fazendo o Drupal normalizar os valores de forma diferente da exportação.
- Um desenvolvedor editou manualmente os arquivos de configuração e introduziu inconsistências.
#!/bin/bash
set -euo pipefail
echo "=== Running Drupal database updates ==="
drush updb --yes
echo "=== Importing configuration from config/sync ==="
drush cim --yes
echo "=== Re-exporting to verify no pending changes ==="
drush cex --yes
echo "=== Checking for config drift ==="
if ! git diff --exit-code config/sync; then
echo ""
echo "❌ FAIL: Configuration drift detected!"
echo " The config in Git does not match what Drupal produced after import+export."
echo " Diff shown above. Fix locally with 'drush cex' and commit."
exit 1
fi
echo "✅ Configuration is clean — no drift detected."7. Implementação no Jenkins (testada em batalha)
7.1 Estrutura do Jenkinsfile
Usamos exclusivamente o declarative pipeline. O scripted pipeline oferece mais flexibilidade, mas o declarative pipeline é mais fácil de ler, mais fácil de validar com jenkins-cli declarative-linter e mais fácil de entender para novos membros da equipe.
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') // segredo do 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 {
// Geração do settings.env.php a partir das credentials/variáveis de ambiente do Jenkins
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: "Config validation failed. See: ${BUILD_URL}",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
}
}
7.2 Particularidades do Jenkins
Shared Libraries
Ao gerenciar mais de dois ou três sites Drupal, extraia a lógica do pipeline Drupal para uma Jenkins Shared Library. Assim, o Jenkinsfile de cada projeto se torna um invólucro fino:
@Library('drupal-pipeline-lib@v2') _
drupalPipeline(
phpVersion: '8.2',
deployBranch: 'main',
dbCredentials: 'drupal-ci-db',
slackChannel: '#deployments'
)Limpeza do workspace
Sempre chame cleanWs() no bloco post { always }. Os agentes do Jenkins acumulam estado de builds anteriores — um settings.env.php remanescente ou um diretório vendor de um build anterior podem afetar silenciosamente o build atual. Seja rigoroso: limpe o workspace.
8. Implementação no GitLab CI
8.1 Estrutura do .gitlab-ci.yml
image: php:8.2-cli
stages:
- build
- test
- validate-config
- deploy
# ── Variáveis compartilhadas ─────────────────────────────────────
variables:
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
APP_ENV: "ci"
MYSQL_DATABASE: "drupal_ci"
MYSQL_ROOT_PASSWORD: "root"
# ── Cache reutilizável ───────────────────────────────────────────
.cache-composer: &cache-composer
cache:
key:
files: [composer.lock]
paths:
- .cache/composer
- vendor/
policy: pull
# ── Estágio de build ────────────────────────────────────────────
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
# ── Estágio de teste ────────────────────────────────────────────
phpunit:unit:
stage: test
<<: *cache-composer
script:
- ./vendor/bin/phpunit --testsuite=unit --log-junit=reports/phpunit.xml
artifacts:
reports:
junit: reports/phpunit.xml
# ── Estágio de validação da configuração ────────────────────────
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
# ── Estágio de deploy ───────────────────────────────────────────
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 # Promoção em um clique, exigindo confirmação manual
script:
- bash ci/drupal-deploy.sh production8.2 Vantagens do GitLab CI
O GitLab CI tem uma série de recursos que deixam esse workflow especialmente limpo:
- Artefatos completos — exportações de configuração que falham são salvas automaticamente e ficam disponíveis na interface do MR sem configuração adicional.
- Cache nativo por
composer.lock— as instalações subsequentes do Composer ficam rápidas. - Visibilidade do pipeline no Merge Request — os desenvolvedores veem o status da validação da configuração direto no MR, antes do merge.
- Bloco Services — o MySQL é executado como sidecar, sem infraestrutura adicional.
- Environments + manual gates — a regra
when: manualno deploy em produção proporciona promoção em um clique, com verificação humana, sem um approval workflow à parte.
#!/bin/bash
set -euo pipefail
TARGET_ENV="${1:-staging}"
echo "=== Deploy para: ${TARGET_ENV} ==="
# Sincronização do artefato construído com o ambiente de destino (rsync, SSH, S3 — adapte à sua hospedagem)
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 "=== Executando comandos pós-deploy em ${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
# Sanity-check final no próprio ambiente de destino
git diff --exit-code config/sync || (echo "❌ Config drift on target!"; exit 1)
drush cr
echo "✅ Deploy concluído."
REMOTE9. Configuração específica de ambiente — sem trapaças
Nem todo valor de configuração precisa ser igual em todos os ambientes. Backends de busca, nível de logging, camadas de cache e endpoints de APIs de terceiros diferem por bons motivos. A questão é como contemplar essas diferenças sem comprometer a integridade do seu pipeline de configuração.
As ferramentas certas: config_split e config_ignore
O config_split permite definir conjuntos de configuração ativos apenas em determinados ambientes. Cada split vive em seu próprio diretório e é ativado via settings.php ou settings.env.php com base em uma variável de ambiente.
langcode: en
status: true
id: development
label: Development
description: 'Configuração ativa apenas em ambientes locais/dev'
folder: '../config/splits/development'
module:
devel: 0
kint: 0
dblog: 0
theme: { }
blacklist: { }
graylist: { }$app_env = getenv('APP_ENV') ?: 'production';
// Ativa o config_split correspondente conforme o 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');Exemplos de split de projetos reais
| Item de configuração | Dev split | Stage split | Prod split |
|---|---|---|---|
| Backend do Search API | Backend de banco de dados | Solr (pequeno) | Solr (cluster de prod) |
| Log de erros | dblog, modo detalhado | syslog | syslog + APM externo |
| Módulos de desempenho | Desativados | Ativados | Ativados + configuração de CDN |
| Transporte de e-mail | Mailhog / null mailer | Mailpit | SMTP / SendGrid |
Abordagens incorretas — não faça assim:
- Editar a configuração diretamente no banco de dados de produção.
- Usar condições
if ($settings['environment'] === 'prod')dentro dosettings.phppara substituir valores de configuração — isso ignora totalmente o sistema de configuração e cria divergências invisíveis em tempo de execução. - Manter branches Git separadas para cada ambiente com arquivos de configuração diferentes.
10. Promoção entre ambientes
Promoção não é deploy. O deploy leva código e configuração para um ambiente. A promoção leva um artefato validado — algo que já passou pelo CI — para o próximo ambiente da cadeia.
Na prática, isso significa que o mesmo Git SHA que foi verificado no dev acaba chegando à produção. Nenhum commit de última hora. Nenhum hotfix que contorna o CI. Nenhum cherry-pick que pula a verificação de configuração.
na feature branch / main
deploy automático no merge
deploy automático a partir de develop
manual gate na main
O mesmo artefato validado passa por todos os ambientes. O CI roda uma vez; seu resultado se propaga adiante.
A filosofia de builds imutáveis significa: o que passou na verificação do CI é o que será implantado. Se um hotfix for realmente necessário, ele passa por uma branch fast-track própria, com sua própria execução de CI — ele não deve contornar a validação.
main com uma verificação obrigatória de status do pipeline. Os "Protected branches" do GitLab e o plugin "GitHub Branch Source" do Jenkins dão suporte a isso. Se o pipeline não passar, o deploy em produção é impossível.11. Tratamento de casos fora do padrão (com os quais você inevitavelmente vai se deparar)
Incompatibilidades de UUID
As entidades de configuração contêm UUIDs. Se você instala um site novo e depois tenta importar a configuração de outra instalação, o Drupal se recusa a fazer a importação por causa de um erro de incompatibilidade de UUID. A solução é sempre instalar com a flag --existing-config ou definir o UUID do site após a instalação:
# Lê o UUID da configuração commitada e o aplica à nova instalação:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yesOrdem de ativação e desativação de módulos
Ao ativar um novo módulo via configuração, o Drupal respeita automaticamente a ordem das dependências durante a execução do drush cim. Porém, se o install hook do módulo gera uma configuração padrão que conflita com o que está em config/sync, pode ser necessário remover essa configuração padrão e refazer a importação. O pipeline detectará isso como deriva de configuração.
Configuração que depende de conteúdo
Alguns itens de configuração fazem referência a conteúdo — por exemplo, um bloco que aponta para um item de menu por ID, ou uma view que filtra dados por um termo de taxonomia. Essas referências podem diferir entre ambientes se o conteúdo foi criado de forma distinta. A solução é gerenciar esse conteúdo por meio de migrações ou de módulos de default content, e não por configuração.
Particularidades da configuração multisite
Em uma configuração multisite, cada site tem seu próprio diretório de sincronização de configuração. O pipeline de CI deve executar a verificação de configuração para cada site, e não apenas para o principal. Use um loop:
#!/bin/bash
set -euo pipefail
for SITE_DIR in web/sites/*/; do
SITE=$(basename "$SITE_DIR")
[[ "$SITE" == "default" ]] && continue
[[ "$SITE" == "simpletest" ]] && continue
echo "--- Checking config for 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 "❌ Deriva de configuração no site: $SITE"
exit 1
fi
done
echo "✅ Todos os sites sem divergências."Atualização do núcleo do Drupal com mudanças de configuração
As atualizações do núcleo às vezes incluem mudanças de schema que afetam a configuração existente. Sempre execute drush updb antes do drush cim e rode drush cex depois, para capturar quaisquer mudanças normalizadas pelo schema. Commite essas mudanças na mesma branch em que ocorre a atualização do núcleo — não deixe o pipeline detectá-las de surpresa.
12. Mecanismos operacionais de segurança
Configuração «somente leitura» em produção
Considere ativar o módulo Config Readonly em produção. Ele impede qualquer mudança de configuração pela UI administrativa — uma restrição rígida que reforça a regra de «nenhuma mudança manual» no nível da aplicação.
if (getenv('APP_ENV') === 'production') {
$settings['config_readonly'] = TRUE;
}Verificação pós-deploy
# Garantir que não sobraram mudanças de configuração pendentes após o deploy:
drush config-status 2>&1 | grep -v 'No differences' && { \
echo "❌ Diferenças de configuração inesperadas detectadas após o deploy"; exit 1; \
} || echo "✅ Configuração sem divergências"
# Verificação de aquecimento do cache:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"Estratégia de rollback
O rollback é uma operação de Git, e não de banco de dados. Se um deploy causou um problema:
- Identifique o último commit SHA estável.
- Execute o pipeline para esse SHA.
- O pipeline reimplanta o artefato previamente validado.
Editar manualmente o banco de dados para desfazer uma importação de configuração nunca é a solução. Isso contorna todos os mecanismos de segurança e normalmente cria mais deriva do que corrige.
Auditoria: quem mudou a configuração, quando e por quê
Como toda mudança de configuração passa pelo Git, o seu log de auditoria é o histórico do Git. Use mensagens de commit significativas e imponha-as por meio de um commit-msg hook ou de uma etapa de lint no CI:
$ git log --oneline config/sync/views.view.articles.yml
a3f8c12 feat(views): add taxonomy filter to articles view (PROJ-421)
9e1b307 fix(views): remove exposed sort causing query timeout (PROJ-389)
4d22a91 chore(config): export after Search API Solr 4.3.0 upgrade13. Erros comuns que não cometemos mais
| Erro | Por que é ruim | Solução |
|---|---|---|
Executar drush cim manualmente no staging ou prod | Importa um estado desconhecido — potencialmente incluindo mudanças locais não commitadas | Apenas o pipeline importa a configuração em ambientes compartilhados |
| Permitir mudanças pela UI no staging | O staging vira fonte da verdade, criando divergências com o Git | Ativar o config_readonly em todos os ambientes compartilhados |
| Esperar que os desenvolvedores «não esqueçam de exportar» | Sob pressão, eles vão esquecer com certeza | Pre-commit hooks avisam; a falha do CI obriga |
| Ignorar a deriva de configuração | Divergências sutis se acumulam até uma quebra crítica | O trio drush cim → cex → git diff --exit-code é obrigatório |
| Branches separadas por ambiente com configuração diferente | O merge vira um pesadelo de conflitos; falta uma única fonte da verdade | Uma única branch de configuração, dividida por config_split |
Pular o drush updb antes do drush cim | Atualizações de schema podem causar erros de importação ou perda silenciosa de dados | Sempre: updb → cim → cex → diff |
14. Resultados após adotar essa abordagem
Após adotar essa abordagem em um portfólio de sites Drupal — de um único site pequeno a uma agência que gerencia dezenas de instalações — os resultados foram consistentes:
drush cim em produção em 18 mesesAlém dos números, o mais importante foi a mudança cultural. As discussões sobre configuração migraram do Slack para o histórico do Git. Em vez de «alguém mudou os estilos de imagem no staging?», a pergunta passou a ser: «Existe um commit para isso?» — e sempre existe, ou a mudança não aconteceu.
Os novos desenvolvedores não precisam mais decifrar regras tácitas do tipo «qual ambiente é o canônico agora». A resposta é sempre uma só — o Git. E o pipeline garante isso.
15. Recomendações finais
- Comece rigoroso e relaxe apenas quando necessário. É mais fácil afrouxar uma regra desnecessária do que endurecer uma que nunca existiu.
- Trate qualquer diff de configuração como falha de build. Sem exceções e sem «a gente corrige depois do release».
- O CI é a fonte da verdade para o deploy, e não o notebook do desenvolvedor, o ambiente de staging ou uma decisão no Slack.
- A sequência
updb → cim → cex → diffé intocável. Se você pular até mesmo uma etapa — está agindo às cegas. - Use o
config_splitpara diferenças reais entre ambientes. Não use condições nosettings.phpcomo atalho. - Ative o config_readonly em todos os ambientes compartilhados. Torne as mudanças acidentais pela UI impossíveis, e não apenas indesejadas.
- Salve as exportações de configuração que falham como artefatos de CI. Os desenvolvedores precisam ver o que exatamente causou a deriva, e não apenas o fato de ela existir.
- O rollback é feito via Git, e não por «operações» no banco de dados. O pipeline é o caminho mais seguro nos dois sentidos.
Tanto o Jenkins quanto o GitLab CI dão conta igualmente bem de impor essa disciplina. O GitLab CI exige menos infraestrutura e tem melhor suporte nativo a artefatos; o Jenkins oferece mais flexibilidade para ambientes corporativos complexos e excelente suporte a shared libraries para padronizar pipelines entre projetos. Escolha a ferramenta adequada à sua organização — os princípios deste artigo se aplicam em qualquer caso.
O objetivo nunca foi tornar a gestão de configuração mais complicada. O objetivo é torná-la rotineira: uma etapa previsível que sempre funciona, em que ninguém pensa e que nunca leva a incidentes às 23h. Com um pipeline de CI corretamente implementado, é exatamente isso que você obtém.
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
projects@drupalbook.org