Continuous integration
Vortex offers continuous integration configurations for GitHub Actions and CircleCI that automate the process of building, testing, and deploying your site.
The workflow structure is identical for both continuous integration providers. Choose one of them and follow its setup instructions.
The continuous integration pipeline consists of multiple jobs executed in a
drevops/ci-runner container to ensure
consistency across runs. Each job installs the
drevops/vortex-tooling
scripts via scripts/vortex-tooling.sh, so the vendor/bin/vortex-* commands
are available before the codebase is assembled.
Workflow structure
Local Development
══════════════════════════════════════ ═══════════════════════════════════════════════════
Developer writes code ──► Build and test locally ──► Commit changes
│
▼
Git Repository
═════════════════════════════════════════════════════════════════════════════════════════
Push to remote branch ──► Open/Update Pull Request
│
▼
┌─ CI Pipeline ────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─ Database Job (Nightly) ───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Scheduled ──► Download production ──► Sanitize database ──► Store database cache │ │
│ │ trigger database Remove sensitive data │ │
│ │ │ │
│ └─────────────────────────────────────┬──────────────────────────────────────────────┘ │
│ │ Provides cached database │
│ ▼ │
│ ┌─ Lint Job ────────────┐ ┌─ Database Job ────────────────┐ ┌─ Audit Job ──────────┐ │
│ │ │ │ ◆ Nightly cache exists? │ │ Composer audit │ │
│ │ Build CLI container │ │ Yes ──► job succeeds │ │ (advisories) │ │
│ │ ▼ │ │ No ──► download, sanitize, │ │ ▼ │ │
│ │ Composer validate │ │ store cache │ │ Gitleaks │ │
│ │ ▼ │ └───────────────┬───────────────┘ │ (committed secrets) │ │
│ │ Composer normalize │ ▼ │ │ │
│ │ ▼ │ ┌─ Build Job ───────────────────┐ │ │ │
│ │ Hadolint │ │ Code assembly │ │ │ │
│ │ ▼ │ │ Docker, Composer deps, │ │ │ │
│ │ DCLint │ │ NPM deps, assets │ │ │ │
│ │ ▼ │ │ ▼ │ │ │ │
│ │ PHPCS │ │ Website setup │ │ │ │
│ │ ▼ │ │ Import cached DB, drush │ │ │ │
│ │ PHPStan │ │ deploy, custom scripts │ │ │ │
│ │ ▼ │ │ ▼ │ │ │ │
│ │ Rector │ │ Testing │ │ │ │
│ │ ▼ │ │ PHPUnit tests ──► Behat tests │ │ │ │
│ │ Twig CS Fixer │ └───────────────┬───────────────┘ │ │ │
│ │ ▼ │ │ │ │ │
│ │ Gherkin Lint │ │ │ │ │
│ │ ▼ │ │ │ │ │
│ │ ESLint / Stylelint │ │ │ │ │
│ │ │ │ │ │ │
│ └──────────┬────────────┘ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ │ │ │
│ ┌─ Deployment Job ─────────────────────────────────────────┐ │ │ │
│ │ │ │ │ │
│ │ Webhook Artifact Lagoon │ │ Does not gate │ │
│ │ Call URL Package artifact Run Lagoon CLI deploy │ │ deployment │ │
│ │ │ │ │ │
│ └──────────────────────────────────────────────────────────┘ └──────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
Hosting Platform
═════════════════════════════════════════════════════════════════════════════════════════
◆ Environment ──No──► Sync DB from production ───┐
exists? │
│ Yes ▼
└──────────────────────────────────► drush deploy ──► Custom scripts ──► Notifications
Available Environments
═════════════════════════════════════════════════════════════════════════════════════════
┊ PR Environment ┊ Dev Staging Production
┊ (auto-removed) ┊ develop branch main branch production branch or tag
1. Lint
- Runs in parallel with other jobs (no dependencies)
- Builds only the CLI container (no database or other services needed)
- Validates Composer configuration
- Lints Dockerfiles and Docker Compose files
- Installs development dependencies
- Runs all code linters: PHPCS, PHPStan, Rector, Twig CS Fixer, Gherkin Lint, ESLint, Stylelint
- Checks that Composer configuration is normalized
2. Database
- Fetches the latest database version based on a caching strategy
- Caches database dumps to speed up the follow-up runs
3. Build
- Runs after the
databasejob - Uses Docker Compose to set up the full environment
- Validates Composer configuration
- Assembles the codebase by installing dependencies
- Provisions a website
- Runs PHPUnit and Jest tests (first container only)
- Checks code coverage and posts a PR comment (first container only)
- Runs BDD tests (distributed across all containers - see Test parallelism)
- Collects and stores test results and artifacts
4. Deployment
- Runs after successful completion of both
buildandlintjobs - Uses the built codebase without development dependencies from the
buildstep - Adds required secrets and environment variables
- Triggers a deployment using a router script - see Deployment
Security audit
Security checks run in their own workflow, separate from the pipeline above, so that a failing audit is never confused with a failing linter and can be re-run on its own:
| Provider | Location |
|---|---|
| GitHub Actions | The Security audit workflow in .github/workflows/audit.yml |
| CircleCI | The audit workflow in .circleci/config.yml |
The workflow runs the same 2 checks in both providers, and needs neither the application containers nor installed dependencies:
composer audit --lockedchecks the packages pinned incomposer.lockagainst published security advisories- Gitleaks scans the codebase for committed secrets
Every check runs even if an earlier one failed, so a single run reports all the
findings at once. The workflow fails if any of the checks failed, unless that
check's _IGNORE_FAILURE variable (see Ignore tool failures)
is set to 1.
It is triggered by the same pushes, pull requests and tags as the main
pipeline, and can also be started on demand - in GitHub Actions from
Actions → Security audit → Run workflow, and in CircleCI by re-running the
audit workflow from the pipeline view.
Because the audit is a separate workflow, it is not a dependency of the
deploy job - a failing audit does not by itself stop a deployment. To block
merges and deployments on it, add its check to the repository's branch
protection rules as a required status check.
Caching strategy
The database is fetched overnight by a scheduled run and cached, so the follow-up continuous integration runs on the same day reuse the cached dump instead of downloading a fresh one.
The cache key is built from a configured cache source branch (the
VORTEX_CI_DB_CACHE_BRANCH variable, develop by default) and a daily
timestamp - every run on any branch reads the same shared cache. If no cache
exists for the current day, the previous day's cache for the same source branch
is used as a fallback.
Database caching speeds up continuous integration runs considerably on projects with a lot of data.
For a project with a large database (over 1 GB), the database import itself may take a long time, so it may be worth packaging the database dump into a container image (overnight) or using a sanitized database dump with only the data the tests require.
Vortex supports both creating and using a database container image with embedded data. You may use MariaDB data container for Drupal with database captured as Docker layers to create an initial database image.
Other tools serve the same goal: Drush GDPR Dumper, for example, removes data during the Drush database export itself, without an intermediate database import step.
Reset the cache
If you need to force a fresh cache (e.g., to pull a new database dump outside of the regular schedule), increment the last segment of the version tag in the cache keys:
# Before
v26.8.0
# After
v26.8.1
The version tag is the Vortex release version (CalVer). Bumping only its last segment keeps a project's own cache resets from colliding with the version shipped by a future Vortex update.
Trigger conditions
Both providers build on branch pushes, pull requests, tags matching semantic
version (1.2.3, 1.2.3-rc.1) or date-based (2023-04-17) patterns, and a
nightly schedule that refreshes the database cache.
The exact branch filters differ per provider:
- GitHub Actions builds pushes to long-lived branches (
production,main,master,develop,release/**,hotfix/**,project/**) and pull requests from any branch, so short-lived branches likefeature/**andbugfix/**trigger builds through their pull requests - see GitHub Actions. - CircleCI builds every pushed branch, and restricts only the deployment job to a configured branch list - see CircleCI.
Test parallelism
The build job runs across multiple parallel containers (2 by default): Behat
scenarios are distributed across all containers, while everything else runs on
the first one. Which container runs which tool is declared in one place with
CI_IS_<TOOL>_RUNNER variables.
➡️ See Test parallelism for the distribution model, balancing Behat scenarios, and adding more containers.
Maintenance
Enable debug mode
To get verbose output when troubleshooting build failures, enable debug mode
by setting the VORTEX_DEBUG variable to 1 in your CI provider's settings.
Runner disk space
Hosted runners come with a fixed amount of disk space, and running out of it is easy to misread: the runner is terminated from the outside, the step that was running never reports an error, and the failure looks like a hang rather than a disk problem. If a build dies during provisioning without reporting an error, suspect the disk first.
The most reliable fix is to reduce what has to fit: a sanitized dump or a database container image instead of a full dump.
- On GitHub Actions, Vortex prints the disk state in every run and can reclaim space held by preinstalled toolchains - see GitHub Actions > Runner disk space.
- On CircleCI, disk space is not tied to the resource class, so upgrading it adds CPU and memory but no extra room for the build.
Update CI runner image
The CI jobs run inside the drevops/ci-runner
container - a Docker image specifically designed for CI job execution. It
provides a consistent, reproducible environment with 25+ pre-installed tools:
- PHP & Node.js - PHP 8.4, Node.js, Composer, npm, Yarn
- Docker tools - Docker, Docker Compose, Docker Buildx
- Code quality - ShellCheck, shfmt, Bats testing framework
- Utilities - Git, curl, rsync, jq, and more
Using this image ensures all CI runs have identical tooling, eliminating environment inconsistencies between local development and CI. It also speeds up builds by avoiding repetitive installation of common tools.
To update to a newer version, change the image tag in your CI configuration
file. The image follows CalVer versioning (e.g., 26.8.0) with
monthly releases.
Ignore tool failures
Sometimes you may want to allow builds to pass despite linter and test failures.
Set the corresponding VORTEX_CI_*_IGNORE_FAILURE variable to 1 to ignore
failures (but still run the tool and see the results in the logs):
| Tool | Purpose | Variable |
|---|---|---|
| Behat | Run BDD acceptance tests | VORTEX_CI_BEHAT_IGNORE_FAILURE |
| Composer normalize | Ensure composer.json is sorted | VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE |
| Composer security audit | Check dependencies for vulnerabilities | VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE |
| Composer validate | Validate composer.json and lock file | VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE |
| DCLint | Lint Docker Compose files | VORTEX_CI_DCLINT_IGNORE_FAILURE |
| ESLint and Stylelint | Lint JavaScript and CSS | VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE |
| Gherkin Lint | Lint Behat feature files | VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE |
| Gitleaks | Scan the codebase for committed secrets | VORTEX_CI_GITLEAKS_IGNORE_FAILURE |
| Hadolint | Lint Dockerfiles for best practices | VORTEX_CI_HADOLINT_IGNORE_FAILURE |
| Jest | Run JavaScript unit tests | VORTEX_CI_JEST_IGNORE_FAILURE |
| PHPCS | Check PHP coding standards | VORTEX_CI_PHPCS_IGNORE_FAILURE |
| PHPStan | Static analysis for PHP | VORTEX_CI_PHPSTAN_IGNORE_FAILURE |
| PHPUnit | Run unit, kernel, and functional tests | VORTEX_CI_PHPUNIT_IGNORE_FAILURE |
| Rector | Check for automated refactoring rules | VORTEX_CI_RECTOR_IGNORE_FAILURE |
| SDC Devel | Validate Single Directory Components | VORTEX_CI_SDC_DEVEL_IGNORE_FAILURE |
| Twig CS Fixer | Lint Twig templates | VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE |
Configure deployment skip conditions
Deployments can be skipped for specific branches or pull requests while their CI checks keep running - see Deployment > Skipping deployments.