Most Drupal developers know how to build features. Fewer have invested in the automation layer that makes those features deployable safely, testable reliably, and maintainable long-term. After running CI/CD pipelines for 30+ production Drupal sites over the past five years, here is the complete automation stack we use โ and the hard-won lessons behind each decision.
1. The Drupal Deployment Stack
A modern Drupal automation stack consists of:
- Version control: Git with a defined branching strategy (GitFlow or trunk-based)
- CI/CD pipeline: GitLab CI (our preference), GitHub Actions, or CircleCI
- Local development: DDEV (standardised across all team members)
- Testing: PHPUnit (unit/kernel/functional), Behat (browser tests), PHPCS (code standards)
- Preview environments: Tugboat or DDEV for PR review
- Hosting: Acquia, Pantheon, or custom AWS/GCP with Nginx + PHP-FPM
- Deployment tool: Drush for database updates and cache management
2. GitLab CI/CD Pipeline
Our standard .gitlab-ci.yml pipeline for a Drupal project runs four stages:
stages:
- validate
- test
- build
- deploy
validate:phpcs:
stage: validate
script:
- vendor/bin/phpcs --standard=Drupal,DrupalPractice
web/modules/custom web/themes/custom
validate:phpstan:
stage: validate
script:
- vendor/bin/phpstan analyse web/modules/custom
test:phpunit:
stage: test
script:
- cp .env.ci .env
- vendor/bin/phpunit --testsuite=unit,kernel
coverage: '/^\s*Lines:\s*\d+.\d+\%/'
test:behat:
stage: test
script:
- vendor/bin/behat --profile=ci --format=progress
deploy:production:
stage: deploy
only: [main]
script:
- ./scripts/deploy.sh production
environment: { name: production, url: https://example.com }
3. Automated Testing Strategy
We enforce three levels of automated tests on every Drupal project:
Unit tests cover pure PHP logic โ service classes, data transformers, utility functions. Fast, no database, no Drupal bootstrap:
// tests/src/Unit/PriceFormatterTest.php
public function testFormatWithCurrencySymbol(): void {
$formatter = new PriceFormatter('INR');
$this->assertEquals('โน1,234.50', $formatter->format(1234.50));
}
Kernel tests test Drupal services with a partial bootstrap โ database available, services available, no HTTP requests. Best for testing custom fields, event subscribers, and plugin logic.
Functional/Behat tests drive a real browser via WebDriver and test editorial workflows end to end:
Scenario: Editor can publish a draft article
Given I am logged in as an editor
When I create a draft article with title "Test Post"
And I click "Submit for Review"
Then I should see "Needs Review" in the moderation state
When I log in as a reviewer
And I publish the article
Then the article should be visible at "/blog/test-post"
4. Database Updates & Config Management
Every deployment runs database updates and config import atomically:
#!/bin/bash
# scripts/deploy.sh
set -e
echo "Putting site into maintenance mode"
drush state:set system.maintenance_mode 1
echo "Pulling code"
git pull origin main
echo "Installing dependencies"
composer install --no-dev --optimize-autoloader
echo "Running database updates"
drush updatedb -y
echo "Importing configuration"
drush config:import -y
echo "Rebuilding caches"
drush cache:rebuild
echo "Taking site out of maintenance mode"
drush state:set system.maintenance_mode 0
echo "Deployment complete"
5. Tugboat PR Preview Environments
Every pull request automatically gets a preview environment via Tugboat. Our .tugboat/config.yml spins up a full Drupal environment with a sanitised copy of the production database within 4โ6 minutes of a PR being opened. This means code reviewers and QA can test changes in a real environment โ not just read code โ before anything merges.
The ROI on Tugboat is exceptional. A single caught regression in a preview environment is worth more than the entire monthly Tugboat bill.
6. Zero-Downtime Deployment
Zero-downtime deployment for Drupal requires handling the window between "old code reading new database" and "new code reading new database." Our approach:
- Deploy new code alongside old code (blue/green or rolling, depending on hosting)
- Run
drush updatedbโ hook_update_N functions must be backwards-compatible - Import configuration (
drush config:import) โ staged-file-based to prevent partial states - Switch traffic to new code
- Rebuild caches with new code active
On Acquia and Pantheon, blue/green deployments are built into the platform. On custom infrastructure, we use Nginx upstream switching or AWS ALB target group swapping.
7. Monitoring and Alerting
Automation doesn't end at deployment. Production monitoring for Drupal sites should include:
- Application performance: New Relic or Datadog for PHP request traces and slow query detection
- Uptime monitoring: Pingdom or UptimeRobot for endpoint health checks every 60 seconds
- Error tracking: Sentry with the Drupal Raven module for PHP exception capture
- Security scanning: Scheduled
drush pm:securityin CI + automated Dependabot PRs - Database backup verification: Daily restore test on a staging environment