Architecture
This page explains how the Vortex template fits together, for developers and leads who want the big picture before they work on a project or while they evaluate the template. It covers the principles behind it, the lifecycle your code follows from your machine to production, and the parts that handle each stage. Each section links to the pages that cover its part in depth.
Design principles
Vortex follows these principles:
- Simple is better than complex: The template encourages clarity, even in customizations.
- Use standard tools: Wherever possible, Vortex follows upstream conventions and avoids reinvention.
- Avoid silent errors: Misconfigurations fail loudly. A script missing a required value stops and names the variable.
- Readability counts: Code and configuration are meant to be understood.
- Explicit logging helps: Scripts log every major step, so you can follow what's going on.
Code lifecycle
Vortex runs your code through the same lifecycle locally, in CI, and in hosting, with the same scripts at every stage. That reduces the chance of an error that appears in 1 environment and nowhere else.
The following diagram covers the whole system, and the sections after it take its stages one at a time:
Local Development
═════════════════════════════════════════════════════════════════════════════════════════
Developer writes code ──► Build and test locally ──► Commit changes
│
▼
Git Repository
═════════════════════════════════════════════════════════════════════════════════════════
Push to remote branch ──► Open/Update Pull Request
│
▼
┌─ CI Pipeline ──────────────────────────────────────────────────────────────────────────────┐
│ │
│ The Lint, Test and Build jobs run in parallel. Deployment runs after all three pass. │
│ │
│ Lint Job │
│ Composer validate ──► Hadolint ──► DCLint ──► Build CLI container ──► │
│ Composer normalize ──► PHPCS ──► PHPStan ──► Rector ──► Twig CS Fixer ──► │
│ Gherkin Lint ──► ESLint / Stylelint │
│ │
│ Test Job │
│ Restore the database cache. On the first build of the day (cache miss), each runner │
│ downloads the production database and exports a clean pre-provision dump; only the │
│ primary runner stores it in the cache for the rest of the day's builds. │
│ ▼ │
│ Build Docker ──► Composer deps ──► NPM deps ──► Assets │
│ ▼ │
│ Import cached database ──► drush deploy ──► deploy steps │
│ ▼ │
│ Jest ──► PHPUnit ──► SDC validation ──► Behat │
│ │
│ Build Job │
│ Assemble and validate the deployable artifact │
│ │
│ Deployment Job (after Lint, Test and Build pass) │
│ Webhook: call URL Artifact: push code package Lagoon: trigger deployment │
│ │
│ Security Audit (separate workflow, does not gate deployment) │
│ Composer audit ──► npm audit ──► Gitleaks │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
Hosting Platform
═════════════════════════════════════════════════════════════════════════════════════════
◆ Environment ──No──► Sync DB from production ───┐
exists? │
│ Yes ▼
└──────────────────────────────────► drush deploy ──► Deploy steps ──► Notifications
Available Environments
═════════════════════════════════════════════════════════════════════════════════════════
┊ PR Environment ┊ Dev Staging Production
┊ (auto-removed) ┊ develop branch main branch production branch or tag
Local development environment
Your project runs locally in containers. Docker Compose starts a stack built from the Lagoon container images that Lagoon runs in production, the same ones CI runs, so your local environment matches CI and Lagoon hosting.
Ahoy wraps multi-step tasks like provisioning,
testing, and database imports in short commands such as ahoy provision
and ahoy test.
Docker Compose is the only local environment Vortex supports, so DDEV and Lando setups aren't covered. The roadmap lists support for both as a goal.
➡️ See Development > Environment
Ignored files
Vortex manages 3 ignore files as deny lists: everything is tracked, built, or deployed by default, and each file lists only what to exclude. An allow list works the other way round, so every addition has to be re-included by hand. The deny-list model follows the "avoid silent errors" principle: a new file shows up by default instead of disappearing until someone remembers to allow it.
The .gitignore file controls what the project repository tracks. It
excludes Composer- and Yarn-generated directories (web/core,
web/modules/contrib, vendor, node_modules), local setting overrides,
caches, and content files. 2 sections stay as small allow lists, because a
deny rule can't express them:
recipes/- Composer installs contributed recipes next to your custom ones, so.gitignoreun-ignores each custom recipe explicitly..claude/- only.claude/settings.jsonand.claude/skills/are tracked by default.
The other 2 ignore files apply the same deny-list model to different targets:
.dockerignorecontrols the container image build context. ➡️ See Tools > Docker.gitignore.artifactcontrols the deployment artifact. ➡️ See Deployment > Artifact
A new file or directory in your project is tracked, built, and deployed automatically. Add an entry to 1 of these files only when a file must stay out of that particular target.
Automation scripts
Vortex automates its common tasks with PHP scripts from the
drevops/vortex-tooling
Composer package, installed to vendor/drevops/vortex-tooling/src/. They
also connect the components together.
The scripts share these traits:
- They run identically across local, CI, and hosting environments.
- They read environment variables to adapt their behavior.
- They're modular and extensible.
The scripts aren't part of the template's own files: they're a dependency.
Your project's composer.json requires drevops/vortex-tooling, with a
^2.0@alpha constraint in the shipped template. When a Vortex release
changes that constraint, Updating Vortex brings the
change into your composer.json.
The entry-point scripts install as Composer binaries at
vendor/bin/vortex-<script>, for example vendor/bin/vortex-provision or
vendor/bin/vortex-deploy, and the Ahoy commands wrap those binaries. The
scripts they hand work to, such as the one for each deployment type, stay
inside the package. Ahoy and CI install the package through
scripts/vortex-tooling.sh before the full composer install runs, so the
binaries are there from the first command.
The package's GitHub repository is a read-only mirror of the
.vortex/tooling/
directory in the Vortex repository. Report bugs and propose changes in
the Vortex issue queue, not
on the mirror.
Never edit the files under vendor/drevops/vortex-tooling/src/: the next
composer update discards your change. Adapt behavior through environment
variables instead. Put logic that runs on every deployment in a
DeployStep plugin in a custom module, which provisioning runs in every
environment. Reach for a
cweagans/composer-patches
patch only when a shipped script itself has to change.
➡️ See Development > Composer > Vortex tooling package for updating the package and Development > Provision > Running custom scripts for extending provisioning.
Customizing scripts
All scripts read their configuration from environment variables, so you can adapt them to a project or an environment.
The CLI writes your project's values into .env during setup, such as the
project machine name and the notification email addresses. Secrets, tokens,
and per-environment values go into your CI or hosting environment instead,
and local overrides go into .env.local, which Git ignores.
➡️ See Development > Variables
Router scripts
Vortex has 6 router scripts: entry points that read your configuration and hand the work to the script for your setup. The caller doesn't need to know how each setup works. Each router picks its script as follows:
vortex-fetch-dbfetches a database from the source inVORTEX_FETCH_DB_SOURCE:url(the default),ftp,s3,container_registry,acquia, orlagoon. Thenonesource skips the fetch.vortex-export-dbexports the database to a dump file, or to a container image whenVORTEX_DB_IMAGEis set.vortex-import-dbimports a dump file, and runs the import inside theclicontainer when you call it from the host.vortex-deployruns each deployment type listed inVORTEX_DEPLOY_TYPES(artifact,lagoon, orwebhook), so the same call deploys to Acquia, Lagoon, or any host with a deployment webhook.vortex-notifysends a notification to each channel listed inVORTEX_NOTIFY_CHANNELS: email, Slack, New Relic, GitHub, Jira, a webhook, or Diffy.vortex-taskruns a hosting operation (copy-db,copy-files, orpurge-cache) with the script for the platform inVORTEX_TASK_PLATFORMorVORTEX_PLATFORM, eitheracquiaorlagoon.
Script architecture diagram
Drupal management
Provisioning
Provisioning is central to how Vortex works. Instead of running Drush
commands by hand, you run 1 script, vortex-provision (ahoy provision
locally), and it handles:
- Importing a database dump, or installing Drupal from a profile instead
- Running updates, config imports, cache rebuilds, and deploy hooks
- Running the
DeployStepplugins of your custom modules, through the Deploy Steps module - Sanitizing a freshly imported database
Because provisioning is centralized, it runs the same way locally, in CI, and in hosting.
Deploy steps carry the automation around provisioning, such as running
migrations conditionally or setting up a development environment. The
template ships examples in the ys_base, ys_search, and ys_migrate
modules.
➡️ See Development > Provision
Settings management
Vortex organizes Drupal settings per environment:
- The
drevops/environment-detectorpackage detects the hosting platform or CI service, resolves the environment type, and applies that platform's settings. The types arelocal,ci,development,preview,stage, andproduction. - The
ENVIRONMENT_TYPEvariable overrides the detected type. Set it in.env.localfor your local stack, or in your hosting environment's variables. - Each module's settings live in their own file in
web/sites/default/includes/modules/, with the per-environment logic inside that file.
Keeping each module's settings in its own file avoids config sprawl and lets you remove a module's settings cleanly when you no longer need them.
PHPUnit tests in tests/phpunit/Drupal/ check that these settings load
correctly in each environment.
➡️ See Development > Settings
Module and theme scaffolds
Vortex includes an example custom module and theme, each with tests. Together, they show the following practices:
- Structuring custom features
- Writing functional, kernel, and unit tests
- Connecting theme assets to a build pipeline
Use these scaffolds as starting points for your own work.
➡️ See Development > Modules > Module scaffold and Development > Themes
Code quality and testing
Vortex ships with pre-configured tools for maintaining code quality:
- PHP CodeSniffer (PHPCS)
- PHPStan
- Rector
- Twig CS Fixer
- ESLint
- Stylelint
- Gherkin Lint
- Hadolint
- DCLint
- SDC Devel
Vortex also ships scaffolds for these test frameworks:
- PHPUnit: Unit, kernel, and functional testing
- Behat: Behavior-driven testing with screenshot capture and extra steps
- Jest: JavaScript unit testing
➡️ See Development > Code quality and Development > Testing
Automated dependency updates
Vortex includes configuration for Renovate to automate dependency upgrades:
- Drupal core minor and patch updates are checked daily, with no waiting period, so security releases arrive on the next run
- Container images, GitHub Actions, and the CI tools are checked daily, and the other PHP and JavaScript packages weekly, as is the Renovate image that CircleCI runs
- Most new releases wait 14 days before they're proposed, so a registry can pull a compromised release first
- Major versions of PHP and JavaScript packages are left for you to upgrade by hand
- The PHP, Node.js, and npm versions aren't updated, and neither are the
packages in any
package.jsonbelow the project root, such as the theme's - PRs include changelogs, pass through your continuous integration pipeline, and can merge automatically after it's green
Renovate runs as the hosted GitHub app or self-hosted in your CI, and the
CLI asks which. It also asks which update groups merge automatically. The
schedules live in renovate.json, so you can tweak them.
➡️ See Development > Dependency updates
Environment variables
Vortex relies on environment variables to configure its behavior across
local, CI, and hosting environments. You define them in the .env file or
set them in your CI and hosting environments, which is where secrets belong.
➡️ See Variables
Continuous integration workflows
Vortex supports GitHub Actions and CircleCI, and the CLI asks which one your project uses. Both pipelines include the following:
- Database fetching and caching, for faster builds
- Full site provisioning
- Code quality checks
- Unit, functional, and behavior tests
- Deployment after the tests pass
- Security audits of Composer and npm packages and a Gitleaks secret scan, in a workflow of their own
Each continuous integration configuration mirrors what happens locally and in hosting.
➡️ See Continuous integration
Hosting integrations
Vortex has built-in support for Acquia and Lagoon hosting. These integrations do the following:
- Trigger provisioning with the same steps used locally and in CI
- Provide deployment pipelines suited to each platform
Other platforms work too, through the artifact and webhook deployment
types, but only Acquia and Lagoon have built-in integrations.
➡️ See Hosting
Documentation & onboarding
Besides this documentation site, Vortex adds a docs/ scaffold to your
repository for the project-specific decisions, unless you answer No to
the CLI's Preserve project documentation? prompt. The scaffold has a
page for each of these topics:
- Testing conventions
- CI, when the project has a CI provider
- Deployment, when the project has a deployment type
- Releasing
- FAQs
The README.md that Vortex writes for your project includes local
setup steps for new team members.
Testing the template
We test the template and its automation scripts at 3 levels:
- Unit tests: PHPUnit tests in
.vortex/tooling/tests/run each shipped script with external commands mocked - End-to-end tests: PHPUnit tests run full build pipelines in real Docker containers
- Real-world validation: The DrevOps website is a production reference site that receives regular upstream updates