Provision
Provisioning turns an assembled codebase into a working Drupal site. By
default it imports the database dump; switch the provision type to profile
and it installs Drupal from a profile instead. Either way, it then runs the
database updates and the configuration import, so the site matches your
code. A site that's already installed keeps its database unless you ask for
a fresh one.
This page is for developers working day to day on a Vortex project, and for the lead who tunes provisioning for each environment. It explains what the provision script does and which variables steer it. It then shows how to fetch, refresh, and export the database, and how to add your own provisioning steps and deploy hooks. How code reaches a hosting environment before provisioning runs there is a separate topic; see Deployment.
A single script does the whole job: vortex-provision, shipped in the
drevops/vortex-tooling
Composer package and installed at vendor/bin/vortex-provision. Your
machine, CI, and every hosting environment run that same script. The steps
stay the same everywhere, and only the environment variables and the
detected environment type change what they do.
Rationaleβ
Drush has a command for each deployment step (drush updatedb,
drush config:import, drush deploy:hook), but running them by hand
assumes the site is already bootstrapped and stable. That assumption breaks
during an initial setup, and in environments that come and go, such as CI
pipelines, container builds, or multisite setups. There, something has to
run the steps in the right order and decide which of them apply.
The provision script is 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.
- It runs the same provisioning logic 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.
Provisioning flowβ
The following diagram shows the steps the script takes and the points where it branches. Environment variables steer most of the branches, and the circled numbers match the numbered variables in Customizing flow:
π Start
β
β π‘ Skip provision? ββYesβββΊ π End
β No
βΌ
β‘ π‘ Provision type = database
β
ββ π‘ Existing site found = YES
β ββ Container image set? βββΊ preserve content (no import), β© sanitize
β ββ β’ Overwrite DB? ββYesβββΊ π’οΈ Attempt import from dump
β β β
β β ββ Dump file exists? βββΊ ποΈ Drop DB βββΊ π’οΈ 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? βββΊ π’οΈ Attempt import from dump
β
ββ Dump file exists? βββΊ ποΈ Drop DB βββΊ π’οΈ 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)
βΌ
β¨ π Repeat configuration import (opt-in)
βΌ
β¬οΈ Import config_split configuration (if the module is enabled)
βΌ
π§Ή 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
The diagram follows the default database provision type. With
VORTEX_PROVISION_TYPE=profile, the branches at the top collapse into 1
decision. A site that isn't installed yet gets a fresh installation from the
profile.
With the profile type, an installed site keeps its database and skips
sanitization, unless VORTEX_PROVISION_OVERRIDE_DB is 1, which reinstalls
it from the profile. Everything after the installation runs the same way
for both types.
With a database container image, an installed site keeps the database the
image provides, without an import. The script still treats that database
as fresh: it sanitizes it and sets VORTEX_PROVISION_OVERRIDE_DB=1 for
your custom scripts.
Right after the skip check (β ), the script checks that the configuration
directory in $settings['config_sync_directory'] exists, and stops with an
error if it doesn't.
Customizing flowβ
These environment variables steer the flow:
VORTEX_PROVISION_SKIP=1
Skip provisioning entirely. The script exits as soon as it starts and leaves the site alone, which helps in an emergency when every kind of automation has to stop.VORTEX_PROVISION_TYPE=profile
Install Drupal from the profile inDRUPAL_PROFILE(defaultstandard) instead of importing adatabasedump. With exported configuration present, it runsdrush site:install --existing-config, so the site installs from your configuration. That suits sites without a persistent database, and it tests that your configuration installs cleanly from scratch. Any value other thanprofilecounts asdatabase, the default.VORTEX_PROVISION_OVERRIDE_DB=1
Replace the database of a site that's already installed. The script drops it and imports the dump again, or reinstalls from the profile. Without it, an installed site keeps its database and only the later steps run..envships with0, the usual choice for hosted environments, and.env.local.examplesets1, so local provisioning starts from a fresh copy.VORTEX_PROVISION_FALLBACK_TO_PROFILE=1
Fall back to a profile installation when the database dump file is missing, or when the container image holds no usable database. The script installs the profile inDRUPAL_PROFILEwithout your exported configuration and enables the Shield module to protect the environment. It then skips every post-provision operation, from database updates to custom scripts. The result is a minimal working Drupal site when no database is available. It applies to thedatabaseprovision type only.VORTEX_PROVISION_POST_OPERATIONS_SKIP=1
Stop right after the database import or profile installation, and skip every later step, from maintenance mode to the final configuration check. CI uses it to cache a database in the state it was fetched, before any updates run.VORTEX_PROVISION_USE_MAINTENANCE_MODE=1
Put the site into maintenance mode right after the database import or installation, and take it out again at the end. Visitors then don't reach a site that's halfway through provisioning. It's on by default:.envsets1, and so does the script when the variable isn't set.VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_UPDATE=1
Fail the provisioning whendrush updatedbchanges active configuration. The script exports the configuration before and after the updates and compares the 2 copies, sodrush config:importcan't silently overwrite what an update hook changed. On failure, it lists the changed files and keeps both exports for you to review. The check needs exported configuration files and is off by default.VORTEX_PROVISION_CACHE_REBUILD_AFTER_DB_UPDATE_SKIP=1
Skip the cache rebuild between the database updates and the configuration import. By default, the script runsdrush updatedb --no-cache-clearand then rebuilds the caches itself, so the configuration import starts from a clean state.VORTEX_PROVISION_CONFIG_IMPORT_REPEAT=1
Run the configuration import a second time, straight after the first. It helps when update hooks introduce configuration that changes what a later import does (for example, newconfig_splitsettings). It's off by default, and like the import itself it needs exported configuration.VORTEX_PROVISION_SANITIZE_DB_SKIP=1
Skip database sanitization. Database sanitization explains when it runs otherwise.VORTEX_PROVISION_VERIFY_CONFIG_UNCHANGED_AFTER_PROVISION=1
Fail the provisioning when the post-provision operations leave active configuration different from the exported files. The check runs last, after the configuration imports, deployment hooks, and custom provision scripts. It fails ifdrush config:statusreports any item in theDifferentstate. Items that exist only in the database or only in the sync directory don't count, because a site can legitimately carry either. Like the update check, it needs exported configuration files and is off by default.
A few more variables shape what gets provisioned:
VORTEX_DB_DIRandVORTEX_DB_FILElocate the dump (./.data/db.sqlby default).VORTEX_PROVISION_DB_DIRandVORTEX_PROVISION_DB_FILEoverride them for provisioning only, andVORTEX_PROVISION_DBtakes a full path to the dump.VORTEX_DB_IMAGE, orVORTEX_PROVISION_DB_IMAGEfor provisioning only, names the container image when the database ships inside one. Setting it sends the flow down the "Container image set?" branches of the diagram.DRUPAL_PROFILE,DRUPAL_SITE_NAME,DRUPAL_SITE_EMAIL, andDRUPAL_ADMIN_EMAILfeed the profile installation. The administrator account is always namedadmin.VORTEX_PROVISION_SCRIPTS_DIRsets where the custom scripts live.VORTEX_PROVISION_LOG=1saves the output asprovision.loginVORTEX_NOTIFY_LOG_DIR, where deployment notifications collect it.
The Variables reference lists the script's variables with
their defaults. Set them in .env to apply them everywhere, or in your CI
or hosting provider's environment variables to change a single environment.
Maintenance modeβ
Maintenance mode keeps visitors out while the database updates and
configuration imports run, so nobody browses a site that's halfway between
2 versions. It's on by default: .env sets
VORTEX_PROVISION_USE_MAINTENANCE_MODE=1, and the script falls back to 1
when the variable isn't set at all. To keep the site online during
provisioning, set it to 0 in .env or in your hosting provider's
environment variables.
If a step fails partway through, the script exits with the site still in maintenance mode. Fix the problem and provision again, or turn maintenance mode off by hand with Drush in the affected environment:
drush maint:set 0
Maintenance mode runs in PHP and needs the database. To keep every request away from PHP, for example during a migration on Lagoon, serve the static maintenance page.
Database sanitizationβ
Sanitization replaces real user data with safe values, so a copy of
production can't email real people from a test run or show their details to
the team. The script runs drush sql:sanitize, which does the following:
- Replaces account email addresses and passwords
- Scrubs the values of user fields
- Removes comment author names and email addresses
- Empties the sessions table
Sanitization changes the imported database, not the dump file, so anyone with access to the dump can still read the sensitive data in it.
If your database holds highly sensitive data, sanitize the dump before it can be downloaded (sanitize on export). Tools such as Drush GDPR Dumper or MTK do this, and they fit into Vortex-based projects without changes to the provisioning process.
Sanitization runs by default whenever provisioning brings in a fresh database: a dump import, a profile installation, or a database container image. A fallback installation is the exception, because it skips every step after the install. Sanitization is also skipped whenever the script keeps an existing database, and that includes a database the hosting platform copied in, for example from production.
Production keeps its own database between deployments, so it isn't
sanitized, and .lagoon.yml also sets VORTEX_PROVISION_SANITIZE_DB_SKIP=1
in production explicitly.
To turn sanitization off, set VORTEX_PROVISION_SANITIZE_DB_SKIP=1 in the
.env file or in your hosting provider's environment variables.
Customizing database sanitizationβ
To adjust what sanitization does, set these variables in the .env file
or in your hosting provider's environment variables:
VORTEX_PROVISION_SANITIZE_DB_EMAIL=user_%uid@your-site-domain.example
Replace every account email address with this pattern. Drush replaces%uidwith the user ID. It also accepts%mail, the original address with@changed to_, and%name, the username with spaces changed to_..envships with this value; without it, the script usesuser+%uid@localhost.VORTEX_PROVISION_SANITIZE_DB_PASSWORD=<random or exact>
Replace account passwords with this exact value. By default the script generates a random number on each run, so no production password works on the sanitized copy.VORTEX_PROVISION_SANITIZE_DB_REPLACE_USERNAME_WITH_EMAIL=1
Replace each username with the user's sanitized email address, so the usernames don't give real names away either. It's off (0) by default.VORTEX_PROVISION_SANITIZE_DB_ADDITIONAL_FILE=./scripts/sanitize.sql
Run the SQL queries in this file after the Drush sanitization. This path is the default, and Vortex shipsscripts/sanitize.sqlwith an example query that blocks user 1. The script skips this step when the file doesn't exist, so deleting the file turns it off.
Whatever you configure, the script then empties the name and email of the
anonymous user (user 0). When DRUPAL_ADMIN_EMAIL is set, it also sets
user 1's email back to that address.
Databaseβ
The running site's database lives inside the database container. The
.data/db.sql file on your machine is a dump: the import commands read it,
and the export command writes new dumps next to it. Remove the container
(ahoy reset does, for example) and the live database goes with it; the
next build re-creates it from the dump.
Fetching and exporting run on the host, because they move dump files around. Refreshing runs inside the containers, because it imports into the running database. The command tabs in the following sections follow that split.
Fetching databaseβ
ahoy fetch-db downloads a database dump into the .data directory.
VORTEX_FETCH_DB_SOURCE sets where the dump comes from, and accepts the
following values:
url, the default: the file atVORTEX_FETCH_DB_URLftp: a file on an FTP serveracquia: a backup of an Acquia environmentlagoon: a dump of a Lagoon environmentcontainer_registry: a database container image, pulled instead of a files3: a file in an S3 bucketnone: no source; you put the dump into.datayourself
Any other value stops the command with an error that lists these values.
Each source reads its own settings, which the
Variables reference lists. The hosting sources copy the
environment named in VORTEX_FETCH_DB_ENVIRONMENT: .env ships with
prod, and the installer sets main for lagoon, because Lagoon names
environments after their branches. Projects that install from a profile
have no ahoy fetch-db, because the installer removes it.
To fetch the dump, run the following command in the project root:
- Ahoy
- Host
# Fetch latest database dump
ahoy fetch-db
# Force a fresh fetch regardless of cache
ahoy fetch-db --fresh
# Fetch latest database dump
./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
Confirm that the output ends with Finished database fetch. and that
.data holds the dump.
When .data already holds a dump, the fetch keeps it and stops, whatever
its age, and the output says Fetch will not proceed. instead.
VORTEX_FETCH_DB_FORCE=1 downloads a new copy anyway, and
.env.local.example sets it, so a .env.local copied from that file always
downloads.
--fresh sets VORTEX_FETCH_DB_FORCE=1 too, along with
VORTEX_FETCH_DB_FRESH=1. That second variable makes Acquia create a new
backup instead of downloading the latest one. On Lagoon, it creates a new
dump instead of reusing the one made earlier that day.
The dump stays in .data instead of going straight into the database, so
you can reuse it: refreshing the local environment doesn't need a new fetch.
To use a dump you downloaded yourself, name it db.sql and put 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. Use ahoy import-db for the raw data
without any configuration changes or updates, and ahoy provision for the
full setup, including configuration and updates.
Import and run updates (ahoy provision)β
ahoy provision runs the whole provisioning flow
inside the CLI container:
- Database import from dump or profile installation
- Database updates with
drush updatedb - Configuration import with
drush config:import, when configuration files exist - Cache rebuilds
- Deployment hooks
- Database sanitization
- Post-provisioning custom scripts
To bring the local environment back to a fully updated state, with a
configured site that's ready for development or deployment, use
ahoy provision. Besides the flow itself, the command does the following:
- Asks for confirmation before it starts
- Reloads the database container from its image first, when
VORTEX_DB_IMAGEis set - Unblocks user 1 and prints a one-time login link at the end
The dump replaces the current database only when no site is installed yet
or VORTEX_PROVISION_OVERRIDE_DB=1 is set, which .env.local.example does
once you copy it to .env.local. Otherwise the site keeps its database and
only the later steps run.
To provision the site, run the following command:
- Ahoy
- Docker Compose
ahoy provision
docker compose exec cli ./vendor/bin/vortex-provision
Confirm that the output ends with Finished site provisioning, followed by
the time it took. With ahoy provision, the one-time login link comes
after it.
Import only (ahoy import-db)β
ahoy import-db imports the raw dump and runs none of the other
provisioning steps:
- Imports the database dump
- Skips database updates
- Skips configuration imports
- Skips cache rebuilds
- Skips deployment hooks
- Skips database sanitization
- Skips post-provisioning scripts
To reset the database contents to the state captured in the dump, use
ahoy import-db. It asks for confirmation, then drops the current database
and loads the dump. A missing or unreadable dump file stops it with
Unable to import database from file. before anything is dropped.
To import a dump, run the following command:
- 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
Confirm that the output ends with Finished database import.
Exporting databaseβ
ahoy export-db saves the current local database as a timestamped dump in
.data, with a name such as export_db_20260101_120000.sql. To export the
database, run the following command:
- Ahoy
- Host
# Export current database to .data directory
ahoy export-db
# Export current database to .data directory
./vendor/bin/vortex-export-db
Confirm that the output includes Exported database dump saved, followed
by the path of the new file.
These dumps let you return the local environment to a known state later:
rename one to .data/db.sql and run ahoy import-db, or pass its path to
ahoy import-db directly. To pick the filename yourself, pass it to the
export: ahoy export-db db.sql writes .data/db.sql. With
VORTEX_DB_IMAGE set, the command exports a database container image
instead of a file.
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. To choose which tables are exported this way, set
VORTEX_EXPORT_DB_FILE_STRUCTURE_TABLES to a comma-separated list of table
names. Each name can use the * wildcard, and the default is cache*. To
export the data of every table, set it to an empty value.
Running custom scriptsβ
Near the end of each run, after sanitization and while the site is still in maintenance mode, the provision script runs your custom scripts. Use them for tasks specific to your project, such as enabling modules conditionally or running migrations in a set order.
To add a custom script, follow these steps:
- In the
scriptsdirectory, create a file whose name starts withprovision-and ends with.sh, such asscripts/provision-50-custom.sh. - Make the file executable:
chmod +x scripts/provision-50-custom.sh. - Run
ahoy provision.
Confirm that the provisioning output shows
Running custom post-install script "./scripts/provision-50-custom.sh".,
followed later by a Completed running of custom post-install script line
for the same file.
The provision script finds the file on each run, so there's nothing to
register. VORTEX_PROVISION_SCRIPTS_DIR changes the directory it looks in
(default ./scripts).
Scripts run in alphabetical order, so start each name with a 2-digit number
to set the order (for example, provision-50-custom.sh and
provision-60-another-custom.sh). Every matching script runs in every
environment, production included, so each script has to check where it
runs; the shipped ones check the environment type first. A script that
exits with an error stops the provisioning.
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β
To act only in some environments, read the environment type that the
Drupal settings file stores in
$settings['environment']:
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
To act only on a fresh database, check VORTEX_PROVISION_OVERRIDE_DB. The
provision script sets it to 1 for your scripts whenever the database is
new: imported from a dump, installed from a profile, or started from a
container image. The following check reads it:
if [ "${VORTEX_PROVISION_OVERRIDE_DB:-0}" = "1" ]; then
echo "> Fresh database detected."
else
echo "> Existing database detected."
fi
The shipped scripts/provision-40-example.sh combines both checks, and
makes a good starting point for your own 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 does the following, in order:
- Creates the content model from the
recipes/pagerecipe - Sets the site name
- Switches the administration interface to the core Navigation module and uninstalls Toolbar
- Installs the contrib, service, and custom site modules
- Runs their deployment hooks
The content model comes first because the site modules attach behavior to
the page content type, and their deployment hooks need it. Replace the
script's operations with your own when the site no longer relies on the
demo content.
Development modulesβ
Vortex ships a scripts/provision-10-enable-dev-modules.sh custom
script that installs the following modules, in this order, each one only
when the installer kept it:
- SDC Devel
- Storybook, on projects with
Storybook enabled. In the
localenvironment, the script also turns on Twig development mode, so component template edits show up without a cache rebuild. - Devel
- Testmode
- Reroute Email, the email safety net
- Generated Content, covered in the next section
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. Their settings files also
add them to $settings['config_exclude_modules'], which keeps them out of
the exported configuration. A configuration import never installs them, so
provisioning is the step that does.
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. That matters for email
rerouting in particular:
Reroute Email's settings
turn rerouting on in every environment outside local, ci, and prod,
but only the development modules script installs the module. 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
that provide the generated content plugins are already enabled by then, so
the module picks up their plugins.
To opt out of the content, set DRUPAL_GENERATED_CONTENT_SKIP=1 in the
.env file or in your hosting provider's environment variables. The module
is still installed, so you can generate the content later from the
administration interface at /admin/config/development/generated-content
or with Drush, for example
drush generated-content:create-content node page 20.
Installing a module that's already installed does nothing, so provisioning a
site that already holds the module leaves its content untouched. It also
means the content appears only on the provision that first installs the
module. A site provisioned once with DRUPAL_GENERATED_CONTENT_SKIP=1
doesn't gain the content when you unset the variable and provision again.
Migrationβ
Projects with the migration feature ship a
scripts/provision-20-migration.sh custom script that runs the content
migration in every environment except prod. It imports the migration
source database dump into the database2 container and probes it for
corruption. It then runs the migrations with drush migrate:import, after
an optional rollback.
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 after the content and migration scripts. 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.
They receive a database whose index tracker state doesn't match their own
(empty) search backend, while every other environment keeps its existing
index.
To opt out of search indexing, set DRUPAL_SEARCH_INDEX_SKIP=1 in the
.env file or in your hosting provider's environment variables.
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.
Hook types overviewβ
Drupal core provides the first 2 hook types in the following table, and Drush adds the third:
| 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.
For changes tied to a deployment, use deploy hooks. Each one runs only once: after it runs, Drush records its name in Drupal's key-value store and skips it from then on.
Example deploy hookβ
The following deploy hook, placed in ys_base.deploy.php, creates an About
Us page in every environment except production:
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 long 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β
To list the pending deploy hooks or run them by hand, use the following Drush commands in the CLI container:
# Show pending deploy hooks.
drush deploy:hook-status
# Run deploy hooks manually (for testing).
drush deploy:hook