The drupalci Pipeline & gitlab_templates
When to Use
Read this when you need to understand which CI jobs run on your merge request, what each job does, and how the shared template system works. This is the reference for interpreting a pipeline result.
How the Pipeline is Wired
Contrib projects opt into the Drupal Association's centrally-managed pipeline by adding a one-line include to their .gitlab-ci.yml. This keeps ~2,000 contrib projects on a shared, maintained pipeline without each project managing its own CI configuration from scratch.
# .gitlab-ci.yml (contrib module)
include:
- project: 'project/gitlab_templates'
file: '/includes/include.drupalci.main.yml'
ref: '$_GITLAB_TEMPLATES_REF'
- project: 'project/gitlab_templates'
file: '/includes/include.drupalci.variables.yml'
ref: '$_GITLAB_TEMPLATES_REF'
- project: 'project/gitlab_templates'
file: '/includes/include.drupalci.workflows.yml'
ref: '$_GITLAB_TEMPLATES_REF'
variables:
_TARGET_CORE: "^11"
_TARGET_PHP: "8.3"
$_GITLAB_TEMPLATES_REF is a DA-managed variable that auto-updates to the latest stable tag (currently v1.15.4). Pinning to a fixed tag requires setting both _GITLAB_TEMPLATES_REF and _CURL_TEMPLATES_REF to match.
Job Set (v1.15.4)
| Stage | Job | Default blocking? |
|---|---|---|
| Build | composer |
Yes |
| Validation | composer-lint |
allow_failure: true |
| Validation | phpcs |
Yes |
| Validation | phpstan |
allow_failure: true |
| Validation | cspell |
allow_failure: true |
| Validation | eslint |
allow_failure: true |
| Validation | stylelint |
allow_failure: true |
| Validation | secret-detection |
allow_failure: true |
| Test | phpunit |
Yes |
| Test | nightwatch |
allow_failure: true |
| Test | test-only changes |
Conditional |
| Contrib | upgrade-status |
Opt-in (manual trigger) |
| Contrib | drupal-cms |
Opt-in |
Critical fact: all linting and validation jobs except composer and phpcs default to allow_failure: true — they are non-blocking. A maintainer opts into enforcement per job:
# Make phpstan blocking on your project
phpstan:
allow_failure: false
v1.15.0 change: OPT_IN_TEST_* variant jobs (previous major, next minor, max PHP) moved to manual trigger. They no longer auto-run. A green pipeline can simply mean they were never triggered — do not imply coverage from opt-in variants unless you explicitly triggered them.
Key Variables
| Variable | Purpose |
|---|---|
_TARGET_CORE |
Drupal core version to test against (e.g. "^11") |
_TARGET_PHP |
PHP version (default 8.2 on D11) |
_TARGET_DB_TYPE |
Database type |
_GITLAB_TEMPLATES_REF |
Template version pin (DA-managed) |
SKIP_PHPCS, SKIP_PHPSTAN, … |
Opt-out a job entirely |
OPT_IN_TEST_PREVIOUS_MAJOR |
Enable previous-major variant (manual trigger) |
OPT_IN_TEST_NEXT_MINOR |
Enable next-minor variant (manual trigger) |
_PHPUNIT_CONCURRENT |
1 → switches phpunit to run-tests.sh concurrency mode |
_PHPUNIT_EXTRA |
Extra args passed to the phpunit job |
SYMFONY_DEPRECATIONS_HELPER |
Controls deprecation handling in tests |
How the phpunit Job Runs
The phpunit job runs:
vendor/bin/phpunit -c web/core/phpunit.xml.dist --webroot=web
When _PHPUNIT_CONCURRENT: 1 it switches to core/scripts/run-tests.sh. The binding constraint is not the wrapper — it is the core phpunit.xml.dist, which carries failOnWarning="true" (and failOnPhpunitWarning in Drupal 11.3.x). Tests fail on warnings regardless of wrapper.
Pinned Tool Versions (resolved from core, never hardcoded)
| Tool | Drupal 11.x | Drupal 10.x |
|---|---|---|
| PHPUnit | ^11.5.50 |
^9.5 / ^9.6 |
| PHPStan | ^1.12.27 \|\| ^2.1.54 |
^1.x only |
mglaman/phpstan-drupal |
^1.3.9 \|\| ^2.0.15 |
^1.x |
drupal/coder |
^8.3.30 |
^8.3.x |
Do not hardcode these — resolve them from the target core's drupal/core-dev at setup time.
Reading a Pipeline Result
Pipeline stages run in order: Build → Validation → Test. Per-job status:
- Green — passed
- Red — failed; if
allow_failure: true, the pipeline continues - Yellow (manual) — opt-in variant, not yet triggered; not covered
- Gray (skipped) — skipped by rule or
SKIP_*variable
A pipeline shows "passed" while allow_failure jobs are red and manual jobs un-run. The MR is mergeable only when all non-allow_failure, non-manual jobs pass. The Test Report tab (from junit artifacts) surfaces per-test failures inline.
How Projects Override the Templates
Jobs are defined via hidden templates (.job-base, .job-rule) using YAML extends: and !reference. Override without copying the template:
# Override a specific job in your .gitlab-ci.yml
phpcs:
allow_failure: false # make non-blocking
phpstan:
variables:
_PHPSTAN_LEVEL: "8" # increase strictness
some-job:
when: never # skip entirely
Common Mistakes
- Reading a "passed" pipeline as full CI coverage when opt-in variant jobs were never triggered — those tests are unrun, not passing.
- Assuming linting failures block the MR — check each job's
allow_failuresetting per project. - Pinning
_GITLAB_TEMPLATES_REFwithout also setting_CURL_TEMPLATES_REF— they must match. - Running phpunit without the core config — the
failOnWarningbehavior lives incore/phpunit.xml.dist, not the wrapper.