| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Drainpipe is a composer package which provides build tool and testing helpers for a Drupal site, including:
composer config extra.drupal-scaffold.gitignore true
composer config --json extra.drupal-scaffold.allowed-packages "[\"lullabot/drainpipe\", \"lullabot/drainpipe-dev\"]"
composer require lullabot/drainpipe
composer require lullabot/drainpipe-dev --devand if using DDEV, restart to enable the added features:
ddev restartDrainpipe will scaffold out various files, most importantly a Taskfile.yml in the root of your repository. Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make. Since it's written in Go, Task is just a single binary and has no other dependencies. It's also cross-platform with everything running through the same shell interpreter.
Drainpipe installs the Task binary for you, no matter if you are using DDEV or not. If you already have Task installed system-wide and are not using DDEV, your installed binary will be linked from the vendor/bin directory.
You can see which tasks are available after installation by running ./vendor/bin/task --list or ddev task --list if you're running DDEV. To get more information on a specific task e.g. what parameters it takes, you can run task [task name] --summary.
Your Taskfile.yml can be validated with JSON Schema:
curl -O https://taskfile.dev/schema.json npx ajv-cli validate -s schema.json -d scaffold/Taskfile.yml
See .github/workflows/ValidateTaskfile.yml for an example of this in use.
Drupal scaffolds are core files automatically placed and updated in the project root by drupal/core-composer-scaffold (documentation).
Scaffold files provided by Drainpipe are located in the main scaffold directory.
To determine where a file is placed, edit the extra.drupal-scaffold section in composer.json. Check how each scaffolded file is defined in /src/ScaffoldInstallerPlugin.php.
A specific file scaffolding can be disabled by mapping it to false under the extra.drupal-scaffold.file-mapping in the project composer.json file. This prevents it from being created or overriden when running composer install or composer update. Note Drainpipe workflows cannot be overridden.
{
"extra": {
"drupal-scaffold": {
"file-mapping": {
"[web-root]/robots.txt": false
}
}
}
}
If you receive an error such as exec format error: ./vendor/bin/task, then you may have the wrong binary for your architecture. If your architecture wasn't detected correctly, please open an issue with the output of php -r "echo php_uname('m');", along with information on your hardware and operating system.
You can override the platform and processor with environment variables DRAINPIPE_PLATFORM and DRAINPIPE_PROCESSOR. Valid platform values are linux, darwin, or windows, and processors are 386, amd64, or arm64. These correspond to builds of upstream dependencies e.g. https://github.com/go-task/task/releases
The Tugboat configuration file is not a static file; it is dynamically generated based on your .ddev/config.yaml file. To see the implementation details, check /src/TugboatConfigPlugin.php.
All Drainpipe components (DDEV, Github Actions, Tugboat) are configured to use the same Node JS major version. This is set in the .nvmrc file, which is scaffolded from the Drainpipe's root directory, but can be overriden to use a different Node version.
If you are using Renovate (for automated dependency updates) you can use/extend our Drupal presets by doing the following:
{
"extends": [
"group:drupal-core"
]
}
This preset provides safe automation with flexibility and control for teams maintaining Drupal applications, minimizing risk by requiring approval for major changes while accelerating security patches through the automerging of minor updates.
The drupal:update command runs drush deploy directly, per the Lullabot ADR on Drupal build steps.
drush deploy
Sites that need to customize the deploy steps (e.g. running config:import twice, or running drush cron after deploy) should replace the drush deploy command using a site-level Drush command file. See the ADR for a full example.
Drainpipe will add .env file support for managing environment variables.
This is only used for locals - other environments such as CI and production should use their native environment variable mechanisms.
This consists of:
"autoload-dev":
{
"files": [
"vendor/lullabot/drainpipe/scaffold/env/dotenv.php"
]
},
This compiles CSS assets using Sass. It also supports the following:
// Base @use "sass/base/**/*";
vars:
DRAINPIPE_SASS: |
web/themes/custom/mytheme/style.scss:web/themes/custom/mytheme/style.css
web/themes/custom/myothertheme/style.scss:web/themes/custom/myothertheme/style.css
assets:
desc: Builds assets such as CSS & JS
cmds:
- yarn install --immutable --immutable-cache --check-cache
- task: sass:compile
- task: javascript:compile
assets:watch:
desc: Builds assets such as CSS & JS, and watches them for changes
deps: [sass:watch, javascript:watch]
JavaScript bundling support is via esbuild.
DRAINPIPE_JAVASCRIPT: | web/themes/custom/mytheme/script.js:web/themes/custom/mytheme/script.min.js web/themes/custom/myotherthemee/script.js:web/themes/custom/myothertheme/script.min.js
assets:
desc: Builds assets such as CSS & JS
cmds:
- yarn install --immutable --immutable-cache --check-cache
- task: sass:compile
- task: javascript:compile
assets:watch:
desc: Builds assets such as CSS & JS, and watches them for changes
deps: [sass:watch, javascript:watch]
This is provided by the separate drainpipe-dev package (so the development/testing dependencies aren't installed in production builds).
All the below static code analysis tests can be run with task test:static
| Test Type | Task Command | Description |
|---|---|---|
| Security | task test:security | Runs security checks for composer packages against the FriendsOfPHP Security Advisory Database and Drupal core and contributed modules against Drupal's Security Advisories. |
| Lint | task test:lint | - YAML lint on .yml files in the web directory - Twig lint on files in web/modules, web/profiles, and web/themes - composer validate These cannot currently be customised. See #9. |
| PHPStan | task test:phpstan | Runs PHPStan with mglaman/phpstan-drupal on web/modules/custom, web/themes/custom, and web/sites at level 6. |
| PHPUnit | task test:phpunit:static | Runs Unit tests in web/modules/custom/**/tests/src/Unit and test/phpunit/**/Unit |
| PHPCS | task test:phpcs | Runs PHPCS with Drupal coding standards provided by [Coder module](https://www.drupal.org/project/coder |
Projects with pre-existing violations can generate a baseline file to suppress them, so CI only fails on new code:
task test:phpstan:generate-baseline git add phpstan.neon phpstan-baseline.neon git commit -m "Add PHPStan level 6 baseline"
This creates phpstan-baseline.neon (the suppressed violations) and phpstan.neon (which includes both phpstan.neon.dist and the baseline). Commit both files. The baseline can be regenerated at any time as legacy violations are resolved.
<rule ref="phpcs.xml.dist"> </rule>
<!-- Custom excludes --> <exclude-pattern>web/sites/sites.php</exclude-pattern>
task test:static includes a check for untracked or modified files in the working tree. If your project intentionally leaves certain files uncommitted (e.g. a local config file that must not be in version control), you can exclude them from this check via composer.json:
"extra": {
"drainpipe": {
"untracked-ignore": [
".tugboat/config.yml"
]
}
}The .ddev directory is always excluded automatically.
Functional tests require some mechanism of creating a functing Drupal site to test against. All the below tests can be run with task test:functional
task test:phpunit:functional
Runs PHPUnit tests in:
You will need to make sure you have a working Drupal site before you're able to run these.
task test:autofix attempts to autofix any issues discovered by tests. Currently, this is just fixing PHPCS errors with PHPCBF.
Generic helpers for deployments can be found in tasks/snapshot.yml, tasks/deploy.yml, and tasks/drupal.yml
| task deploy:git | Pushes a directory to a git remote |
| task drupal:composer:development | Install composer dependencies |
| task drupal:composer:production | Install composer dependencies without devDependencies |
| task drupal:export-db | Exports a database fetched with a *:fetch-db command |
| task drupal:import-db | Imports a database fetched with a *:fetch-db command |
| task drupal:install | Runs the site installer |
| task drupal:maintenance:off | Turn off Maintenance Mode |
| task drupal:maintenance:on | Turn on Maintenance Mode |
| task drupal:update | Run Drupal update tasks after deploying new code |
| task snapshot:archive | Creates a snapshot of the current working directory and exports as an archive |
| task snapshot:directory | Creates a snapshot of the current working directory |
Databases are by default fetched to /var/www/html/files/db/db.sql.gz, this can be overridden with a variable in Task:
`task drupal:import-db DB_DIR="/var/www/htdocs"`
When creating a snapshot of the current working directly files can be excluded using a .drainpipeignore file in the root of the repository that uses the same format as .gitignore, e.g.
# Files that won't be deployed to Pantheon /.ddev /.github /.yarn /files/db /tests /.env /.env.defaults /README.md /Taskfile.yml *.sql *.sql.gz
This folder can then be deployed to a remote service either as an archive, or pushed to a git remote with task deploy:git.
Pantheon specific tasks are contained in tasks/pantheon.yml. When any Pantheon CI configuration is present in composer.json, Drainpipe automatically adds the following to your Taskfile.yml's includes section. You can also add it manually:
includes:
pantheon: ./vendor/lullabot/drainpipe/tasks/pantheon.yml| task pantheon:fetch-db | Fetches a database from Pantheon. Set PANTHEON_SITE_ID in Taskfile vars |
| and optionally ENVIRONMENT to override the default value of live |
See below for CI specific integrations for hosting providers.
When using Pantheon with Drainpipe, the Pantheon site should be configured to override some of Pantheon's default behaviors. Because Drainpipe installs composer dependencies, Pantheon's Integrated Composer should be disabled. Add build_step: false to your pantheon.yml file:
---
api_version: 1
build_step: falseAdditionally, Pantheon sites start with "Autopilot", which provides updates from the Drupal upstream. Usually this feature conflicts with an external Git repository such as GitHub or GitLab. It is recommended to disable this by setting an empty upstream with terminus.
ddev ssh terminus site:upstream:set [site_name] empty
pantheon-systems/drupal-integrations is a package that provides essential Pantheon functionality. It is strongly recommended to install it.
composer require pantheon-systems/drupal-integrations
Acquia specific tasks are contained in tasks/acquia.yml. Add the following to your Taskfile.yml's includes section to use them:
includes:
acquia: ./vendor/lullabot/drainpipe/tasks/acquia.yml| task acquia:fetch-db | Fetches a database from Acquia. Set ACQUIA_ENVIRONMENT_ID in Taskfile vars, along with ACQUIA_API_KEY and ACQUIA_API_SECRET as environment variables |
⚠️ Beta Notice: The Acquia integration is currently in beta. While we strive to maintain stability, you may encounter unexpected issues. Please report any problems you encounter through our issue tracker.
To enable auto configuration of Acquia Cloud settings:
"extra": {
"drainpipe": {
"acquia": {
"settings": true
},
}
}Add the following to composer.json for generic GitHub Actions that will be copied to .github/actions/drainpipe in your project:
"extra": {
"drainpipe": {
"github": []
}
}They are composite actions which can be used in any of your workflows e.g.
- uses: ./.github/actions/drainpipe/set-env
- name: Install and Start DDEV
uses: ./.github/actions/drainpipe/ddev
with:
git-name: Drainpipe Bot
git-email: no-reply@example.com
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
ssh-known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
Tests can be run locally with act:
# Turn off ddev so it doesn't get confused between your real host and the
# "host" inside of act. Do this again and when done and you want to run
# the app on your host again.
ddev poweroff
# Windows
act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:runner-latest -j Static-Tests
# Mac with Docker Desktop
act --container-options "--group-add $(stat -f %g /var/run/docker.sock)" \
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:runner-latest \
-j Static-Tests
# Mac with OrbStack
export DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}')
act --container-options "--group-add $(stat -f %g ~/.orbstack/run/docker.sock)" \
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:runner-latest \
-j Static-Tests
# Mac with Lima
# Act gets confused with socket permissions. Run act from inside the lima VM.
# Run `lima` to shell into the VM, then install act following the docs or from
# https://github.com/nektos/act/releases.
# As well, comment out the chown in `.github/actions/drainpipe/ddev/action.yml`.
act --container-options "--group-add $(stat -c %g /var/run/docker.sock)" \
-P ubuntu-latestt=ghcr.io/catthehacker/ubuntu:runner-latest \
-j Static-Tests
# Linux
act --container-options "--group-add $(stat -c %g /var/run/docker.sock)" \
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:runner-latest \
-j Static-Tests
# Using an alternate image like WarpBuild
`-P warp-ubuntu-latest-x64-2x-spot=ghcr.io/catthehacker/ubuntu:runner-latest`.
Workflows for running static and functional tests can be added with the following configuration:
"extra": {
"drainpipe": {
"github": ["TestStatic", "TestFunctional"]
}
}The build process for the functional tests will use task build:ci:functional, falling back to task build:dev, and then task build. The static tests should not require a build step.
These workflow files will continue to be managed by Drainpipe and cannot be overridden. If you wish to do so then it's recommended you maintain your own workflows for testing.
"extra": {
"drainpipe": {
"github": ["Security"]
}
}Runs security checks for composer packages and Drupal contrib, as well as posting a diff of composer.lock as a review comment.
Security checks rely on composer audit. This means test:security task will exit with error if your dependencies have known vulnerabilities. Sometimes, this is not desirable: think of a component which is required by Drupal core - you can not upgrade it directly, because it is pinned to a specific version by Drupal in the first place. To prevent these kind of situations from failing the security check, you can configure composer audit to ignore specific vulnerabilities when scanning your dependencies. To do so, the simplest way is to modify your composer.json file to add a list of vulnerabilities that should not trigger an error when running test:security:
"config": {
"audit": {
"ignore": ["CVE-1234", "GHSA-xx", "PKSA-yy"]
}
}For more details about how to configure the ignore feature for composer audit, check the Composer docs.
The Security workflow also includes a Zizmor static analysis job (ZizmorAnalysis) that scans your GitHub Actions workflow files for security issues. Zizmor results are uploaded to GitHub's code scanning dashboard, which requires Code Security to be enabled for your repository. If it is not enabled, the ZizmorAnalysis job will fail with: "Code Security must be enabled for this repository to use code scanning."
The Security workflow also includes a Gitleaks job (Gitleaks) that scans every PR and push to the default branch for accidentally committed secrets — API keys, tokens, credentials, and similar sensitive values. Findings are uploaded to GitHub's code scanning dashboard under the Gitleaks category.
Suppressing false positives: Drainpipe scaffolds a .gitleaks.toml at the project root that pre-configures allowlists for patterns common in Drupal projects (contributed modules, Drupal core, GitHub Actions template expressions, and placeholder values). Add [[allowlists]] blocks to that file to suppress additional false positives specific to your project. Use [extend] with useDefault = true to inherit all default rules and layer your allowlists on top:
[[allowlists]]
description = "Allow example values in documentation"
regexes = [
'''(?i)example''',
'''(?i)replace[-_.]?me''',
]
[[allowlists]]
description = "Allow test fixtures"
paths = [
'''tests/fixtures/''',
]Recommended: one-time full history scan: When first adopting Gitleaks, run a scan of your full git history to check for secrets that may already be committed:
# Ensure you have a full clone (not shallow)
git fetch --unshallow
# Scan full history and save results to a JSON report
gitleaks detect --source . --report-format json --report-path gitleaks-history.jsonReview gitleaks-history.json and for each finding: rotate any credentials that are still valid; for false positives, add the pattern to .gitleaks.toml. Note that rewriting git history to remove secrets is disruptive — rotating the credential is the most effective remediation.
Post a sticky comment in the Pull Request with a markdown table of any changes detected in yarn.lock or package-lock.json files.
"extra": {
"drainpipe": {
"github": ["LockfileDiff"]
}
}To scaffold Pantheon composite actions for use in your own workflows (without a managed review app workflow), add "Actions":
"extra": {
"drainpipe": {
"github": {
"pantheon": ["Actions"]
}
}
}This scaffolds .github/actions/drainpipe/pantheon/ (setup-terminus, push, clone-env, update, review) along with pantheon.yml and .drainpipeignore, without installing any workflow file. Use this when you manage your own deployment workflow and want Drainpipe's composite actions as building blocks.
To enable deployment of Pantheon Review Apps (Multidev environments per pull request):
"extra": {
"drainpipe": {
"github": {
"pantheon": ["ReviewApps"]
}
}
}The deploy steps delegate to task pantheon:prepare-multidev, task pantheon:drupal-update, and task pantheon:lock-env in tasks/pantheon.yml — customize behavior there.
By default, the review app job blocks the CI runner for up to 10 minutes while it waits for Pantheon to finish syncing code (terminus workflow:wait). The AsyncDeploy option eliminates this wait by splitting the job in two:
Add "AsyncDeploy" alongside "ReviewApps" in your composer.json:
"extra": {
"drainpipe": {
"github": {
"pantheon": ["ReviewApps", "AsyncDeploy"]
}
}
}Then run composer install. The following files are scaffolded or replaced:
DDEV vs. bare runner: Drainpipe automatically selects the right workflow variant. If .ddev/config.yaml is present in your project, the DDEV-based workflow is scaffolded and the build runs inside DDEV as usual. If DDEV is not configured, the bare-runner variant is used instead — in this case your task build target must be executable on a standard ubuntu-24.04 runner without any system dependencies provided by DDEV (e.g. specific PHP extensions, Node versions, or services).
Drainpipe does not auto-edit pantheon.yml because it is version-controlled and site-specific. Add the following to your site's pantheon.yml:
workflows:
sync_code:
after:
- type: webphp
description: Notify CI after Pantheon code sync
script: private/scripts/drainpipe_notify_github.phpImportant: the path is private/scripts/ — not web/private/scripts/. Pantheon resolves this path relative to the web root when web_docroot: true is set. Using the web/ prefix is a common misconfiguration that silently prevents the hook from running.
The Quicksilver script reads credentials from Pantheon Secrets at runtime using pantheon_get_secret(). Set them with Terminus before the first deployment:
terminus secret:set <site> drainpipe_github_token <PAT-with-repo-scope>
terminus secret:set <site> drainpipe_github_repo owner/repoNote: GITHUB_TOKEN (the automatic Actions token) cannot be used here because Quicksilver runs outside GitHub's infrastructure. You must create a personal access token with repo scope.
Note: terminus secret:set requires the terminus-secrets-manager-plugin to be installed wherever Terminus runs (CI runner or local workstation). You can make it available in CI by adding it to the TERMINUS_PLUGINS repository variable.
This is an important difference from the default blocking mode:
| Blocking mode | Async mode | |
|---|---|---|
| Job 1 | Appears as a PR check; can block merging | Appears as a PR check; can block merging |
| Job 2 | N/A (single job) | Does NOT appear as a PR check — only visible in the GitHub Deployments panel (environment badge) and the Actions tab |
In async mode, a failure in Job 2 (e.g. drush update fails) will not block a merge even if the review app check is set as required in branch protection. Monitor the Deployments panel on the PR to confirm Job 2 succeeds before merging.
The async pipeline has six observable states. The table below explains each and where to find logs.
| State | What you see | Where to look |
|---|---|---|
| Job 1 fails (build or git push error) | PR check fails; deployment marked failure | Job 1 CI run — click "View deployment" in the PR Deployments panel |
| Pantheon sync fails (internal error after push) | Deployment stays in_progress, then auto-resolved to error by the watchdog | Pantheon dashboard → site → multidev → Workflows tab; or run terminus workflow:list <site>.<env> locally |
| Quicksilver fires but fails (bad secrets, network, non-2xx response) | Same as above | Pantheon dashboard → Workflows tab (Quicksilver echo output is in the workflow log) |
| Dispatch accepted but Job 2 doesn't start | Same as above | Pantheon dashboard confirms sync succeeded; check Actions tab → PantheonReviewAppsPostDeploy runs |
| Job 2 fails (drush update error, etc.) | Deployment marked failure | Job 2 CI run — click "View deployment" in the PR Deployments panel |
| Job 2 succeeds | Deployment marked success with environment URL | Deployment panel shows ready; "View deployment" links to the Job 2 run |
log_url on every status: each deployment status update now carries a log_url pointing directly to the CI run that created it. The "View deployment" link in the PR Deployments panel always takes you to the relevant logs.
Job 1 step summary: when Job 1 exits in async mode it writes a summary to the Actions job page with a direct link to the Pantheon dashboard workflow logs and the terminus workflow:list command for local diagnosis.
The scaffolded PantheonReviewAppsWatchdog.yml workflow runs every 30 minutes and automatically resolves deployments stuck in in_progress. When a deployment has been in_progress longer than the threshold, the watchdog:
The threshold defaults to 20 minutes and can be overridden with a repository variable:
PANTHEON_ASYNC_WATCHDOG_THRESHOLD_MINUTES=30
You can also trigger the watchdog manually via workflow_dispatch with a custom threshold_minutes input.
If a developer pushes twice in quick succession, a second Quicksilver event fires while Job 2 is still running. The cancel-in-progress: true concurrency group on the post-deploy workflow cancels the older Job 2 automatically — only the latest sync triggers a Drush update.
To add Acquia specific GitHub actions, add the following to composer.json:
"extra": {
"drainpipe": {
"github": {
"acquia": ["Deploy"]
}
}
}Then run composer install. A Deploy to Acquia workflow at .github/workflows/AcquiaDeploy.yml will be added (with its dependent actions).
After the Github Actions Integration is merged, you can deploy to Acquia using the UI or the Github CLI (gh).
Github repository settings
In your Github repository settings you need to create the following:
Task settings
When a deployment is made, you must run your own code before and after the deployment. To do so, define your in your /Taskfile.yml the following:
acquia:deploy:before
acquia:deploy:before
desc: "Before the code is deployed to Acquia, run these commands on the GitHub Actions runner"
cmds:
# This task is run on the GitHub Actions runner
# It is a good moment to run tasks like build
# so we can later create the artifact for the Acquia environment.
- task: build
acquia:deploy:after
Runs after the code is switched on the Acquia environment and before drupal:update is called automatically by Drainpipe. Use this for any steps that must happen between the code switch and the Drupal update, such as enabling maintenance mode or syncing files from another environment.
acquia:deploy:after:
desc: "After the code is switched on the Acquia environment, run these commands"
cmds:
# This task runs on the Acquia environment. The site alias is passed
# automatically so commands like drush run against the correct environment.
- task: drupal:maintenance:on
Add the following to composer.json for GitLab helpers:
"extra": {
"drainpipe": {
"gitlab": []
}
}This will import scaffold/gitlab/Common.gitlab-ci.yml, which provides helpers that can be used in GitLab CI with includes and references, or scaffold/gitlab/DDEV.gitlab-ci.yml if you are using DDEV.
include:
- local: '.gitlab/drainpipe/DDEV.gitlab-ci.ymll'
variables:
DRAINPIPE_DDEV_GIT_EMAIL: drainpipe-bot@example.com
DRAINPIPE_DDEV_GIT_NAME: Drainpipe Bot
build:
stage: build
interruptible: true
script:
- !reference [.drainpipe_setup_ddev, script]
- composer install
- ddev restart
- ddev drush site:install minimal -y
- echo "\$settings['config_sync_directory'] = '../config';" >> web/sites/default/settings.php
- ddev drush config:export -y
- ddev task update
Available variables are:
| Variable | |
|---|---|
| DRAINPIPE_DDEV_SSH_PRIVATE_KEY | SSH private key used for e.g. committing to git |
| DRAINPIPE_DDEV_SSH_KNOWN_HOSTS | The result of running e.g. ssh-keyscan -H -p 2222 codeserver.dev.$PANTHEON_SITE_ID.drush.in |
| DRAINPIPE_DDEV_GIT_EMAIL | E-mail address to use for git commits |
| DRAINPIPE_DDEV_GIT_NAME | Name to use for git commits |
| DRAINPIPE_DDEV_COMPOSER_CACHE_DIR | Set to "false" to disable composer cache dir, or another value to override the default location of .ddev/.drainpipe-composer-cache |
| DRAINPIPE_DDEV_VERSION | Install a specific version of DDEV instead of the latest |
Runs composer audit and posts a composer lock diff as a merge request comment. Requires GITLAB_ACCESS_TOKEN variable to be set with api scope.
"extra": {
"drainpipe": {
"gitlab": ["Security"]
}
}To enable deployment of Pantheon Review Apps (Multidev environments per merge request):
"extra": {
"drainpipe": {
"gitlab": {
"pantheon": ["Deploy", "ReviewApps"]
}
}
}This will setup Merge Request deployment to Pantheon Multidev environments. See [scaffold/gitlab/gitlab-ci.example.yml] for an example. You can also just include "Deploy" which will give you helpers that you can include and reference for tasks such as setting up Terminus. See scaffold/gitlab/Pantheon.gitlab-ci.yml.
The deploy steps delegate to task pantheon:prepare-multidev, task pantheon:drupal-update, and task pantheon:lock-env in tasks/pantheon.yml — customize behavior there.
Add the following to composer.json to enable Bitbucket Pipelines support:
"extra": {
"drainpipe": {
"bitbucket": []
}
}The pipeline image (php:8.5-cli) includes only a minimal set of PHP extensions. Before running composer install, setup-php.sh automatically detects missing extensions by running composer check-platform-reqs against composer.lock and installs them via mlocati/docker-php-extension-installer.
To ensure an extension is installed, declare it in the require block of your project's composer.json:
"require": {
"ext-zip": "*",
"ext-gd": "*"
}To identify which extensions your project needs, run:
ddev composer check-platform-reqs
For extensions that cannot be declared in composer.json (e.g. proprietary agents such as New Relic or Tideways), set the DRAINPIPE_PHP_EXTENSIONS Bitbucket repository variable to a space-separated list of extension names:
newrelic tideways_xhprof
Acquia Deploy triggers a deployment to the Acquia dev environment whenever a PR is merged to main, equivalent to the GitHub Actions AcquiaDeploy workflow.
To enable Acquia Deploy on Bitbucket:
"extra": {
"drainpipe": {
"bitbucket": ["AcquiaDeploy"],
"acquia": { "settings": true }
}
}"extra": {
"drainpipe": {
"bitbucket": ["AcquiaDeploy"],
"bitbucketDeployBranch": "master",
"acquia": { "settings": true }
}
}"bitbucket": ["AcquiaReviewApps", "AcquiaDeploy"]How it works
When a PR is merged to main the branches.main pipeline:
User-defined hooks
Add these tasks to your project's Taskfile.yml as needed:
tasks:
acquia:deploy:before:
desc: "Runs before the Acquia deploy snapshot — compile assets, etc. Do NOT call drupal:composer:production here."
cmds:
- task: build
acquia:deploy:after:
desc: "Runs after code is switched on Acquia — typically calls 'task drupal:update'"
cmds:
- task: drupal:update
vars:
site: "{{.site}}"Bitbucket repository variables
Add these in your Bitbucket repository settings under Repository variables:
| Variable | Secured | Description |
|---|---|---|
| ACQUIA_API_KEY | Yes | Acquia API Key |
| ACQUIA_API_SECRET | Yes | Acquia API Secret |
| ACQUIA_SSH_PRIVATE_KEY | Yes | SSH private key (base64-encoded) with Acquia Git access |
| ACQUIA_SITE_GROUP | No | Application/site group name (e.g. mysite from mysite.dev) |
| BITBUCKET_USERNAME | No | Bitbucket username — required for commit status API calls |
| BITBUCKET_APP_PASSWORD | Yes | Bitbucket App Password with pullrequest:read scope — required for commit status |
| DRAINPIPE_PHP_EXTENSIONS | No | Space-separated list of additional PHP extensions to install beyond those declared in composer.json (see PHP extensions) |
Note: BITBUCKET_COMMIT is injected automatically by Bitbucket Pipelines and does not need to be set manually.
Note: When both AcquiaReviewApps and AcquiaDeploy are configured, composer install generates a single merged bitbucket-pipelines.yml containing all pipeline sections. Do not edit this file manually — changes will be overwritten on the next composer install.
Acquia Review Apps create an Acquia Cloud Development Environment (CDE) for every pull request, equivalent to Pantheon Multidev environments.
To enable Acquia Review Apps on Bitbucket:
"extra": {
"drainpipe": {
"bitbucket": ["AcquiaReviewApps"],
"acquia": { "settings": true }
}
}How it works
When a pull request is opened or updated, the pull-requests pipeline:
Stale CDEs are not deleted automatically on PR close. See Scheduling the cleanup pipeline below.
Bitbucket repository variables
Add these in your Bitbucket repository settings under Repository variables:
| Variable | Secured | Description |
|---|---|---|
| ACQUIA_API_KEY | Yes | Acquia API Key |
| ACQUIA_API_SECRET | Yes | Acquia API Secret |
| ACQUIA_SSH_PRIVATE_KEY | Yes | SSH private key (base64-encoded) with Acquia Git access |
| ACQUIA_APP_UUID | No | Application UUID from the Acquia Cloud console |
| ACQUIA_SITE_GROUP | No | Application/site group name (e.g. mysite from mysite.dev) |
| BITBUCKET_USERNAME | No | Bitbucket username — required for commit status and cleanup API calls |
| BITBUCKET_APP_PASSWORD | Yes | Bitbucket App Password with pullrequest:read scope — required for commit status and cleanup |
| ACQUIA_SOURCE_ENVIRONMENT | No | Environment to copy the database from (default: dev) |
| ACQUIA_REVIEW_RUN_INSTALLER | No | Set to "true" to run drush site:install --existing-config instead of copying the database |
| DRAINPIPE_PHP_EXTENSIONS | No | Space-separated list of additional PHP extensions to install beyond those declared in composer.json (see PHP extensions) |
Note: BITBUCKET_WORKSPACE, BITBUCKET_REPO_SLUG, BITBUCKET_COMMIT, and BITBUCKET_PR_ID are injected automatically by Bitbucket Pipelines and do not need to be set manually.
Scheduling the cleanup pipeline
Bitbucket does not trigger pipelines on PR close. To avoid accumulating stale CDEs, schedule the acquia-review-apps-cleanup custom pipeline:
Known limitations
The AsyncDeploy option is also available for GitLab. Add it alongside "ReviewApps":
"extra": {
"drainpipe": {
"gitlab": {
"pantheon": ["Deploy", "ReviewApps", "AsyncDeploy"]
}
}
}Then run composer install. The following files are scaffolded or replaced:
Drainpipe does not auto-edit pantheon.yml because it is version-controlled and site-specific. Add the following to your site's pantheon.yml:
workflows:
sync_code:
after:
- type: webphp
description: Notify CI after Pantheon code sync
script: private/scripts/drainpipe_notify_gitlab.phpImportant: the path is private/scripts/ — not web/private/scripts/. Pantheon resolves this path relative to the web root when web_docroot: true is set. Using the web/ prefix is a common misconfiguration that silently prevents the hook from running.
terminus secret:set <site> drainpipe_gitlab_url https://gitlab.com
terminus secret:set <site> drainpipe_gitlab_project_id <numeric-project-id>
terminus secret:set <site> drainpipe_gitlab_trigger_token <pipeline-trigger-token>
terminus secret:set <site> drainpipe_gitlab_ref mainNote: Job 2 (post-deploy) is triggered via a pipeline trigger and runs on the main branch, not the MR branch. This is intentional — Job 2 only needs composer install to obtain the drush/task binaries; it does not rebuild the project. Do not expect Job 2 to run on the MR branch.
Note: terminus secret:set requires the terminus-secrets-manager-plugin. Add it to the TERMINUS_PLUGINS CI variable to make it available in CI.
By default, every push to an open pull request / merge request wipes the multidev's database and files and re-clones them from the source environment (e.g. live). This guarantees that reviewers always see content that mirrors production, but it is the slowest part of the deployment: cloning a large database can add several minutes to every CI run.
Set PANTHEON_SKIP_WIPE_MULTIDEV to 'true' (GitHub) or "true" (GitLab) to change this behaviour:
When to use it: PANTHEON_SKIP_WIPE_MULTIDEV=true is a good default for projects where reviewers do not depend on a fresh production database for each review, or where the live database is large enough to make cloning noticeably slow. Keep it at the default false when it is important that each push reflects the current production content (e.g. for content-heavy sites where QA involves checking migrations or editor workflows).
Add the following to composer.json to add Tugboat configuration:
{
"extra": {
"drainpipe": {
"tugboat": {}
}
}
}Then, run ddev composer install to generate:
The following will be autodetected based on your .ddev/config.yml:
Additionally, Pantheon integration can be added:
{
"extra": {
"drainpipe": {
"tugboat": {
"pantheon": true
}
}
}
}This will install Terminus in the Tugboat environment. Add TERMINUS_MACHINE_TOKEN as a Tugboat environment variable and set PANTHEON_SITE_ID in your Taskfile.yml vars. Then add a sync:tugboat task to fetch the database during Tugboat preview builds:
sync:tugboat:
desc: "Fetches a database from Pantheon and imports it in Tugboat"
vars:
DB_DIR: /var/lib/tugboat/files/db
cmds:
- task: pantheon:fetch-db
- task: drupal:import-db
Similarly, Acquia integration can be added:
{
"extra": {
"drainpipe": {
"tugboat": {
"acquia": true
}
}
}
}This will install Acquia CLI (acli) in the Tugboat environment. Add ACQUIA_API_KEY and ACQUIA_API_SECRET as Tugboat environment variables and set ACQUIA_ENVIRONMENT_ID in your Taskfile.yml vars. Then add a sync:tugboat task:
sync:tugboat:
desc: "Fetches a database from Acquia and imports it in Tugboat"
vars:
DB_DIR: /var/lib/tugboat/files/db
cmds:
- task: acquia:fetch-db
- task: drupal:import-db
When using MySQL as the database engine in DDEV, Tugboat can be configured to use the percona Docker image instead of mysql:
{
"extra": {
"drainpipe": {
"tugboat": {
"percona": true
}
}
}
}For the main php service, you can override any build phase template by copying it to .tugboat/drainpipe-templates/. Example:
mkdir -p .tugboat/drainpipe-templates cp vendor/lullabot/drainpipe/scaffold/tugboat/templates/php-init.yml.twig \ .tugboat/drainpipe-templates/
Edit .tugboat/drainpipe-templates/php-init.yml.twig to add, remove, or modify commands, then regenerate the Tugboat configuration file with ddev composer install.
Available Templates:
Tugboat specific tasks are contained in tasks/tugboat.yml. Add the following to your Taskfile.yml's includes section to use them:
includes:
tugboat: ./vendor/lullabot/drainpipe/tasks/tugboat.yml| Task | Action | Usage |
|---|---|---|
| task tugboat:drush-uli-ready | Configures Drush with the Tugboat service URL for the environment. | Run it once as part of your Tugboat build or online commands defined at .tugboat/config.yml |
It is assumed the following tasks exist:
The build, sync, and update tasks can be overridden with sync:tugboat, build:tugboat, and update:tugboat tasks if required (you will need to re-run composer install to regenerate the Tugboat scripts if you are adding this task to your Taskfile.yml for the first time).
sync:
desc: "Fetches a database from Pantheon and imports it"
cmds:
- task: pantheon:fetch-db
- task: drupal:import-db
sync:tugboat:
desc: "Fetches a database from Pantheon and imports it in Tugboat"
cmds:
- task: pantheon:fetch-db
vars:
DB_DIR: /var/lib/tugboat/files/db
- task: drupal:import-db
vars:
DB_DIR: /var/lib/tugboat/files/db
💡 composer install should be re-run if any changes are made to the DDEV configuration.
You can hook into the init step of any service by adding them to your Taskfile.yml. These additional commands will be run at the end of the init phase for the specific Tugboat service.
Supported services:
Example:
tugboat:php:init:
cmds:
- apt-get install -y libldap2-dev
- docker-php-ext-install ldap
You can also add an online step to the php service by adding a task named online:tugboat and re-running composer install.
Drainpipe will fully manage your .tugboat/config.yml file, you should not edit it. The following keys can be added to your config.yml via a .tugboat/config.drainpipe-override.yml file:
php: aliases: urls: screenshot: visualdiff: solr: commands: checkout: depends: volumes: environment: aliases: urls:
By default, Drainpipe configures Tugboat environments to capture email using Tugboat's built-in mail capture service. This automatically configures:
If you need to use a different mail configuration (e.g., for testing with a specific mail service or module), you can override these settings in your custom settings file (typically web/sites/default/settings.tugboat.php or web/sites/default/settings.php).
For example, to use a different mail backend:
// Override the default Drainpipe mail configuration.
// This should come after including settings.tugboat.php
if (getenv('TUGBOAT_REPO')) {
// Example: Use a custom mail transport
$config['system.mail']['interface']['default'] = 'my_custom_mailer';
// Example: Configure Symfony Mailer differently
$config['symfony_mailer.settings']['default_transport'] = 'custom_transport';
$config['symfony_mailer.mailer_transport.custom_transport']['plugin'] = 'smtp';
$config['symfony_mailer.mailer_transport.custom_transport']['configuration']['host'] = 'custom.smtp.example.com';
$config['symfony_mailer.mailer_transport.custom_transport']['configuration']['port'] = '587';
}This repo is public.
Please be careful to remove sensitive customer specifics when posting Issues or comments.
First time contributors need a maintainers approval for automated tests to run. (This is so we aren't at risk of getting a big CI bill accidentally, or maliciously.)
Peer Reviewing by looking at PR code changes is nice.
Testing PR code changes on real sites is extra beneficial.
In order to test local changes follow the instructions for the test script.
These are guidelines for conducting peer reviews on automated dependency update pull requests created by Renovate.
All automated updates submitted by Renovate undergo a series of automated tests via GitHub Actions. These tests are designed to ensure compatibility and stability with the new versions of dependencies.
All Renovate peer reviews regardless if they're a minor or patch release require:
Some dependencies allow multiple versions, like "drush/drush": "^10|^11|^12".
Occasionally, tests may fail due to transient issues or flakiness in the test suite. In such cases:
Review the Automated Update Pull Request (PR):
Assess Test Results:
Read the Dependency Change Logs:
Final Decision:
When making a release, increase the version based on https://semver.org/
MAJOR version when you make incompatible API changes MINOR version when you add functionality in a backward compatible manner PATCH version when you make backward compatible bug fixes
Specifically for drainpipe, when a new "check" is added, that might break builds in projects, that would usually be a MINOR release, with a release note about the change.
Before making a new release, post in the lullabot internal #devops slack channel to coordinate with other maintainers.
To generate new NPM package releases:
| Back | FazBrowse Home | New Git URL |