Skip to main content
Version: v2

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 .gitignore un-ignores each custom recipe explicitly.
  • .claude/ - only .claude/settings.json and .claude/skills/ are tracked by default.

The other 2 ignore files apply the same deny-list model to different targets:

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-db fetches a database from the source in VORTEX_FETCH_DB_SOURCE: url (the default), ftp, s3, container_registry, acquia, or lagoon. The none source skips the fetch.
  • vortex-export-db exports the database to a dump file, or to a container image when VORTEX_DB_IMAGE is set.
  • vortex-import-db imports a dump file, and runs the import inside the cli container when you call it from the host.
  • vortex-deploy runs each deployment type listed in VORTEX_DEPLOY_TYPES (artifact, lagoon, or webhook), so the same call deploys to Acquia, Lagoon, or any host with a deployment webhook.
  • vortex-notify sends a notification to each channel listed in VORTEX_NOTIFY_CHANNELS: email, Slack, New Relic, GitHub, Jira, a webhook, or Diffy.
  • vortex-task runs a hosting operation (copy-db, copy-files, or purge-cache) with the script for the platform in VORTEX_TASK_PLATFORM or VORTEX_PLATFORM, either acquia or lagoon.
Script architecture diagram
Diagram of scriptsDiagram of scripts

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 DeployStep plugins 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-detector package detects the hosting platform or CI service, resolves the environment type, and applies that platform's settings. The types are local, ci, development, preview, stage, and production.
  • The ENVIRONMENT_TYPE variable overrides the detected type. Set it in .env.local for 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.json below 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

➡️ See Contributing > Maintenance > Template