Provision
The provisioning process sets up a Drupal site on an already assembled codebase. It either imports an existing database from the dump or installs a fresh instance of Drupal using a profile. It then runs the necessary configuration import and database updates.
The provision script handles this process. It ships via the
drevops/vortex-tooling
Composer package and is installed at vendor/bin/vortex-provision.
The script automates the Drupal site setup, so every environment runs the same steps without manual work.
Provisioning flowβ
The diagram below shows the generic flow of the steps that the provision
script takes. Environment variables and other conditions can change the
execution path.
The numbered steps refer to the environment variables described in the next section.
π Start
β
β π‘ Skip provision? ββYesβββΊ π End
β No
βΌ
β‘ π‘ Provision type = database
β
ββ π‘ Existing site found = YES
β ββ Container image set? βββΊ preserve content (no change)
β ββ β’ Overwrite DB? ββYesβββΊ ποΈ Drop DB βββΊ π’οΈ Attempt import from dump
β β β
β β ββ Dump file exists? βββΊ π’οΈ Import DB
β β ββ Dump file missing?
β β ββ β£ Fallback? ββYesβββΊ π¦ Install from profile β (then β€ = Yes)
β β ββ β£ Fallback? ββNoββββΊ π EXIT 1 (fail) β
β ββ else βββΊ preserve content, β© skip sanitization
β
ββ π‘ Existing site found = NO
ββ Container image set?
β ββ β£ Fallback? ββYesβββΊ π¦ Install from profile β (then β€ = Yes)
β ββ β£ Fallback? ββNoββββΊ π EXIT 1 ("corrupted image") β
ββ Container image not set? βββΊ ποΈ Drop DB βββΊ π’οΈ Attempt import from dump
β
ββ Dump file exists? βββΊ π’οΈ Import DB
ββ Dump file missing?
ββ β£ Fallback? ββYesβββΊ π¦ Install from profile β (then β€ = Yes)
ββ β£ Fallback? ββNoββββΊ π EXIT 1 (fail) β
ββ after provisioning (both DB and profile paths) ββ
β€ π‘ Skip other operations? ββYesβββΊ π End
β No
βΌ
β₯ π§ Enable maintenance mode
βΌ
π Set site UUID from configuration (if config files present)
βΌ
π Run DB updates
βΌ
β¦ π‘ Verify config unchanged? ββConfig changedβββΊ π EXIT 1 (fail) β
β Config unchanged (or check disabled)
βΌ
β§ π§Ή Rebuild caches after DB updates (skippable)
βΌ
β¬οΈ Import configuration (if config files present)
βΌ
β¬οΈ Import config_split configuration (if the module is enabled)
βΌ
β¨ π Repeat configuration import (opt-in)
βΌ
π§Ή Rebuild caches
βΌ
π Run deployment hooks
βΌ
β© π· Run DB sanitization
βΌ
βοΈ Run custom scripts
βΌ
β₯ π§ Disable maintenance mode
βΌ
βͺ π‘ Verify config unchanged? ββConfig changedβββΊ π EXIT 1 (fail) β
β Config unchanged (or check disabled)
βΌ
π End
Customizing flowβ
You can control the provisioning flow using the following environment variables:
VORTEX_PROVISION_SKIP=1
Kill-switch to skip provisioning. The script exits immediately after start. Useful in emergencies when any kind of automation needs to be disabled.VORTEX_PROVISION_TYPE=profile
Install from a Drupalprofileinstead of importing from adatabasedump. Useful for building sites without the persistent DB and/or test profile configuration installation.VORTEX_PROVISION_OVERRIDE_DB=1
Drop an existing database before importing from dump/installing from profile. This is useful when an already provisioned environment requires a fresh database to be imported.VORTEX_PROVISION_FALLBACK_TO_PROFILE=1
Automatically fall back to installing from profile if the database dump file or container image isn't available. The site is installed from the configured profile, and the Shield module is enabled to protect the environment. All post-provision operations (configuration import, database updates, deployment hooks, etc.) are skipped. This provides a minimal working Drupal site when no database is available.VORTEX_PROVISION_POST_OPERATIONS_SKIP=1
Skip configuration imports, database updates, and other post-provisioning steps.VORTEX_PROVISION_USE_MAINTENANCE_MODE=1
Enable maintenance mode right after the site is bootstrappable and disable it at the end. Useful when you want to prevent users from accessing the site while it's being provisioned.VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE=1
Verify that active configuration wasn't changed by database updates. When enabled and config files are present, the provision fails ifdrush updatedbmodifies active configuration, preventingdrush config:importfrom silently overwriting those changes.VORTEX_PROVISION_CACHE_REBUILD_AFTER_DB_UPDATE_SKIP=1
Skip the cache rebuild that runs between database updates and configuration import. By default, caches are rebuilt afterdrush updatedbto ensure a clean state before importing configuration.VORTEX_PROVISION_CONFIG_IMPORT_REPEAT=1
Repeat the configuration import after the initial import. Useful when database update hooks introduce new configuration that affects later configuration imports (for example, new config_split settings). Disabled by default.VORTEX_PROVISION_SANITIZE_DB_SKIP=1
Disable database sanitization.VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_PROVISION=1
Verify that active configuration wasn't changed by the post-provision operations. When enabled and config files are present, the provision fails if any configuration item reported bydrush config:statusis in theDifferentstate. The check runs after the configuration imports, deployment hooks, and custom provision scripts have finished. Items that exist only in the database or only in the sync directory aren't treated as a failure.
These variables can be set in your .env file to apply them globally or set
in your CI or hosting provider's specific environment based on your needs.
Maintenance modeβ
During the provisioning process, you may want to enable maintenance mode to prevent users from accessing the site while it's being updated.
To enable maintenance mode, set the
VORTEX_PROVISION_USE_MAINTENANCE_MODE=1 environment variable in your
.env file to apply it globally or set it in your hosting provider's
specific environment.
Database sanitizationβ
The provision script includes a step to sanitize the database after
provisioning. In non-production environments, it replaces real email
addresses, passwords, user information, and other sensitive data with safe,
generic values. As a result, testing can't accidentally send emails to real
users or expose private data.
Sanitization takes place only after the database is imported, so anyone with access to the dump file can still see sensitive data.
If your database has highly sensitive data, consider sanitizing the database dump before it can be downloaded (sanitize on export). Tools such as Drush GDPR Dumper or MTK are available for this purpose. These tools can be integrated into Vortex-based projects without changing the provisioning process.
The database sanitization step is enabled by default on all environments
except production. To disable database sanitization, set
VORTEX_PROVISION_SANITIZE_DB_SKIP=1 in the .env file or in your hosting
provider's specific environment.
Customizing database sanitizationβ
Place these variables in the .env file or in your hosting provider's
specific environment to further customize the database sanitization:
VORTEX_PROVISION_SANITIZE_DB_EMAIL=user_%uid@your-site-domain.example
Replace all emails with a tokenized email string.VORTEX_PROVISION_SANITIZE_DB_PASSWORD=<random or exact>
Replace passwords with a random or exact value.VORTEX_PROVISION_SANITIZE_DB_REPLACE_USERNAME_WITH_EMAIL=0
Replace username with email. Useful to also sanitize user names.VORTEX_PROVISION_SANITIZE_DB_ADDITIONAL_FILE=./scripts/sanitize.sql
Path to a file with custom sanitization SQL queries.
Databaseβ
The running site's database lives inside the database container. The
.data/db.sql file on the host is only a dump used for imports and exports.
Removing the container (for example, with ahoy reset) discards the live
database, and the next build re-creates it from the dump.
Fetching and exporting run on the host (they move dump files around), while refreshing runs inside the containers (it imports into the running database). The tabs below reflect that difference.
Fetching databaseβ
To fetch the database with the latest data from the production environment,
fetch the latest database dump into the .data directory.
- Ahoy
- Host
# Fetch latest database dump (uses cache if fetched today)
ahoy fetch-db
# Force a fresh fetch regardless of cache
ahoy fetch-db --fresh
# Fetch latest database dump (uses cache if fetched today)
./vendor/bin/vortex-fetch-db
# Force a fresh fetch regardless of cache
VORTEX_FETCH_DB_FRESH=1 VORTEX_FETCH_DB_FORCE=1 \
./vendor/bin/vortex-fetch-db
The database dump is stored in the .data directory instead of being
directly imported into the local environment, so it can be cached and
reused. You don't need to fetch it every time you refresh the local
environment.
You can manually fetch the database dump from the production environment,
name it db.sql, and place it in the .data directory.
Refreshing databaseβ
You can load the dump from .data into the running database container in 2
ways, depending on what you need.
Import and run updates (ahoy provision)β
ahoy provision imports the dump and applies all the provisioning steps:
- Database import from dump or profile installation
- Database updates via
drush updatedb - Configuration import via
drush config:import(if config files present) - Cache rebuilds
- Deployment hooks
- Post-provisioning custom scripts
Use it any time you need the local environment reset to a fully updated state: a fully configured site ready for development or deployment.
- Ahoy
- Docker Compose
ahoy provision
docker compose exec cli ./vendor/bin/vortex-provision
Import only (ahoy import-db)β
ahoy import-db imports the raw dump without running any updates,
configuration imports, or scripts:
- Imports the database dump
- Skips configuration imports
- Skips database updates
- Skips post-provisioning scripts
Use it to quickly reset the database contents to the state captured in the dump.
- Ahoy
- Docker Compose
# Import database only from default dump in .data/db.sql
ahoy import-db
# Import database only from a specific dump file
ahoy import-db path/to/dump.sql
# Import database only from default dump in .data/db.sql
docker compose exec cli ./vendor/bin/vortex-import-db
# Import database only from a specific dump file
docker compose exec cli ./vendor/bin/vortex-import-db path/to/dump.sql
Use ahoy import-db when you only need the raw database data without any
configuration changes or updates.
Use ahoy provision when you want the full setup including configuration
and updates.
Exporting databaseβ
Export timestamped database dumps from the local environment.
- Ahoy
- Host
# Export current database to .data directory
ahoy export-db
# Export current database to .data directory
./vendor/bin/vortex-export-db
You can use these dumps to restore the local environment to a specific
state: rename the dump file to .data/db.sql and run the import command.
Cache tables are exported with their structure but without their data,
because Drupal rebuilds them on demand and their contents would only inflate
the dump. Set VORTEX_EXPORT_DB_FILE_STRUCTURE_TABLES to a comma-separated
list of table names, each of which may use the * wildcard, to choose which
tables are exported this way. Set it to an empty value to export the data of
every table.
Rationaleβ
While Drush provides individual commands for deployment steps (such as
drush updatedb, drush config:import, and drush deploy:hook), using
them directly assumes the site is already in a bootstrapped, stable state.
In practice, especially during initial setup or provisioning in dynamic
environments (like CI pipelines, container builds, or multisite setups),
additional orchestration is needed.
The provision script provides that orchestration:
- It bootstraps the environment by importing a database dump or installing a fresh Drupal instance from a profile.
- It handles conditional logic for different runtime scenarios, such as skipping provisioning, enforcing fresh database imports, or using maintenance mode.
- The same provisioning logic runs across local, CI, staging, and production environments.
- It runs custom post-provisioning scripts, so you can add project-specific logic like enabling test modules or running migrations.
Running custom scriptsβ
The provision script can execute custom scripts after all provisioning
steps. Use them to automate additional tasks specific to your project, such
as conditionally enabling modules or running migrations in a specific order.
To run custom scripts, create a new file in the scripts directory with the
provision- prefix and the .sh extension, and make it executable with
chmod +x scripts/provision-50-custom.sh. The script is automatically
discovered and executed. (The discovery directory can be changed with the
VORTEX_PROVISION_SCRIPTS_DIR variable, default ./scripts.)
We recommend using a 2-digit suffix to control the order of execution (for
example, provision-50-custom.sh and provision-60-another-custom.sh).
Vortex ships the following scripts:
| Script | Purpose | Present when |
|---|---|---|
provision-00-enable-demo-modules.sh | Enables the modules and content model the demo site uses | Always; the installer trims its operations to the selected modules |
provision-10-enable-dev-modules.sh | Enables the development modules and generates content | Any of devel, sdc_devel, generated_content, testmode, or reroute_email is selected, or Storybook is enabled; removed by the installer otherwise |
provision-20-migration.sh | Runs the content migration | The migration feature is selected; removed by the installer otherwise |
provision-30-search-index.sh | Rebuilds the search index | The Solr service is selected; removed by the installer otherwise |
provision-40-example.sh | A runnable example that performs no operations | Always; copy it as a starting point or remove it |
provision-50-storybook.sh | Generates the stories and builds the Storybook component library | Storybook is selected; removed by the installer otherwise |
Conditional executionβ
You may choose to only perform an action based on a specific environment
(the value of $settings['environment'] is populated by the Drupal
settings file):
environment="$(drush php:eval "print \Drupal\Core\Site\Settings::get('environment');")"
if echo "${environment}" | grep -qxF -e local -e ci -e dev -e stage; then
echo "> Running custom script for local, ci, dev or stage environment."
# Place your commands here.
else
echo "> Skipping custom script for ${environment} environment."
fi
You may also conditionally perform an action based on whether the database is freshly imported or not:
if [ "${VORTEX_PROVISION_OVERRIDE_DB:-0}" = "1" ]; then
echo "> Fresh database detected."
else
echo "> Existing database detected."
fi
Expand below to see a provision scaffold script that you can use as a starting point for your custom scripts:
Example of a custom provision script
#!/usr/bin/env bash
##
# Example of the custom per-project command that will run after website is installed.
#
# Clone this file and modify it to your needs or simply remove it.
#
# For ordering multiple commands, use a two-digit suffix for clarity and consistency.
# This approach ensures a clear sequence and avoids potential ordering issues.
#
# Example:
# - provision-40-example.sh
# - provision-50-example.sh
# - provision-60-example.sh
set -eu
[ "${VORTEX_DEBUG-}" = "1" ] && set -x
# ------------------------------------------------------------------------------
# @formatter:off
info() { printf " ==> %s\n" "${1}"; }
note() { printf " %s\n" "${1}"; }
task() { printf " > %s\n" "${1}"; }
pass() { printf " + %s\n" "${1}"; }
fail() { printf " ! %s\n" "${1}"; exit "${2:-1}"; }
# @formatter:on
drush() { ./vendor/bin/drush -y "$@"; }
# ------------------------------------------------------------------------------
info "Started example operations."
# Get the current environment from Drupal settings.
environment="$(drush php:eval "print \Drupal\Core\Site\Settings::get('environment');")"
note "Environment: ${environment}"
# Perform operations based on the current environment.
if ! echo "${environment}" | grep -qxF -e local -e ci -e dev -e stage; then
note "Skipped example operations in production environment."
exit 0
fi
note "Running example operations in non-production environment."
task "Performing an example operation."
note "Replace this with your own commands."
pass "Performed an example operation."
# Conditionally perform an action if this is a "fresh" database.
if [ "${VORTEX_PROVISION_OVERRIDE_DB:-0}" = "1" ]; then
note "Fresh database detected. Performing additional example operations."
else
note "Existing database detected. Performing additional example operations."
fi
info "Finished example operations."
Demo modulesβ
Vortex ships a scripts/provision-00-enable-demo-modules.sh custom
script that stands up the demo site. It creates the content model, sets the
site name, and switches the administration interface to the core Navigation
module. It also installs the contrib, service, and custom site modules
before running their deployment hooks.
It runs first so that the content model exists before the modules that depend on it are installed. Replace its operations with your own once the site stops relying on the demo content.
Development modulesβ
Vortex ships a scripts/provision-10-enable-dev-modules.sh custom
script that installs the development modules
(Devel,
SDC Devel, and
Testmode) and the
Reroute Email safety net.
Projects with Storybook enabled also install the
Storybook module here and, in
the local environment, turn on Twig development mode, so component
template edits show up without a cache rebuild.
scripts/provision-50-storybook.sh only generates the stories and builds
the component library.
These modules live in their own script so that they stay enabled after the demo and example scripts are adapted or removed. They're also the modules kept out of the exported configuration, so provisioning is the only place that can install them.
The demo and development modules scripts both run only in the local, ci,
dev, and stage environments, and skip every other environment type. A
project that introduces a custom environment type has to add it to both
scripts to have these modules installed there. This includes the email
rerouting that
the settings file enables
for every environment outside local, ci, and prod. To opt out of
either set of modules, remove the corresponding script.
Generated Contentβ
Projects with the Generated
Content module install it
from the same scripts/provision-10-enable-dev-modules.sh custom script,
after the development modules and before the search index is rebuilt.
The module creates content from its own hook_modules_installed() when
GENERATED_CONTENT_CREATE=1 is set, so the script installs it with that
variable and the content appears as part of the install. The site modules
providing the generated content plugins are already enabled by then, so
their plugins are picked up.
To opt out of the content, set DRUPAL_GENERATED_CONTENT_SKIP=1 in the
.env file or in your hosting provider's specific environment. The module
is still installed, so content can be generated later from the admin
interface or with drush generated-content:create-content.
Installing a module that's already installed does nothing, so provisioning a
site that already holds the module leaves its content untouched. This also
means the content is created only on the provision that first installs the
module. A site provisioned once with DRUPAL_GENERATED_CONTENT_SKIP=1
doesn't gain the content by unsetting the variable and provisioning again.
Migrationβ
Projects with the migration feature ship a
scripts/provision-20-migration.sh custom script that runs the content
migration. It imports the migration source database dump into the
database2 container, probes it for corruption, and runs the migrations via
drush migrate:import, with an optional rollback before the import.
On projects with the Solr service, the script disables the search server while the migrations run, so migrated entities aren't indexed one at a time. The search indexing script rebuilds the index straight after.
β‘οΈ See Migrations for the script's configuration variables and how to add your own migrations.
Search indexingβ
Projects with the Solr service ship with a
scripts/provision-30-search-index.sh custom script that rebuilds the
search index at the end of provisioning. It resets the Search API index
tracker and re-indexes the site content.
The script runs only in the local, ci, dev, and stage environments,
as these environments receive a database with an index tracker state that
doesn't match their own (empty) search backend. Other environments are
skipped to preserve the existing index.
To opt out of search indexing, set DRUPAL_SEARCH_INDEX_SKIP=1 in the
.env file or in your hosting provider's specific environment.
Deploy hooksβ
Update hooks automate database schema changes, data migrations, and environment-specific deployment tasks, so a change ships together with the code that needs it instead of being applied by hand.
Drupal provides several types of hooks for database updates.
Hook types overviewβ
| Hook type | Use case | Execution | File location |
|---|---|---|---|
hook_update_N() | Database schema changes, data migrations | drushΒ updatedb | MODULE.install |
hook_post_update_NAME() | Entity updates and other module operations | drushΒ updatedb | MODULE.post_update.php |
hook_deploy_NAME() | Environment-specific deployment tasks | drushΒ deploy:hook | MODULE.deploy.php |
All these hooks run automatically during the
provisioning process: drush updatedb runs the update
and post-update hooks, and drush deploy:hook runs the deploy hooks.
To automate changes during site deployments, use deploy hooks. Deploy hooks run only once: Drupal tracks which hooks have been executed by name.
Example deploy hookβ
function ys_base_deploy_create_about_page(): string {
$environment = \Drupal\Core\Site\Settings::get('environment');
// Conditional execution based on environment.
if ($environment === ENVIRONMENT_PROD) {
return 'Skipped in production environment';
}
// Check if the About Us page already exists.
$node = \Drupal::entityTypeManager()
->getStorage('node')
->loadByProperties(['type' => 'page', 'title' => 'About Us']);
if (!empty($node)) {
return 'About Us page already exists';
}
$node = \Drupal\node\Entity\Node::create([
'type' => 'page',
'title' => 'About Us',
'body' => [
'value' => 'This is the About Us page content.',
'format' => 'basic_html',
],
]);
$node->save();
return 'Created About Us page';
}
The shipped Drupal helpers module provides static facade helpers for the common deploy hook operations. With them, a hook stays a few lines instead of repeating the entity API boilerplate every time:
use Drupal\drupal_helpers\Helper;
function ys_base_deploy_create_default_tags(): string {
Helper::term()->createTree('tags', ['News', 'Events', 'Blog']);
return 'Created the default tags.';
}
Debugging commandsβ
# Show pending deploy hooks.
drush deploy:hook-status
# Run deploy hooks manually (for testing).
drush deploy:hook