Drupal 8 Deployment Scripts

Who, What, Why, When, and Where

Deploying the latest changes to your site can take many forms. Whether you copy files via FTP 😱, manually pull changes via git, or have an automated deployment infrastructure, every process involves a similar set of steps that are often executed via drush.

Standardizing these steps with a version controlled script is a great way to add consistency and repeatability to the deployment process. Then the question become, what are the steps, what order should they be executed in, when are some steps excluded, and why does this order matter?

Setup

This setup is not strictly needed, but it simplifies the commands later in the process and removes the risk of trying to execute commands from the wrong directory. The example below assumes the use of Composer and the execution of a Composer-installed version of drush:^10.0.

# Define the path to the application.
WEB_DIR=/path/to/docroot/folder

# Store the path to the drush command.
DRUSH="$WEB_DIR/../vendor/bin/drush"

Steps

Our examples assume a shell-based deployment script, but the same methodology applies whether you define your commands in yml, bash, or any other tool.

Display Site Status

We begin by displaying the site status to aid in any subsequent debugging.

# Output information about to site to aid in debugging
# failed deployments.
"$DRUSH" --root="$WEB_DIR" core:status

Enable Maintenance Mode

Before any other steps are taken we put the site into maintenance mode to restrict database activity. This step may not be needed on sites where there is very little editorial activity.

# Put the site into maintenance mode to prevent conflicts
# from occurring during database updates.
$DRUSH--root="$WEB_DIR" state:set system.maintenance_mode 1

Run Drush Deploy

The commands below are included as a part of drush deploy. We will address them individually below to walk through what each one does and why it is included.

drush updatedb --no-cache-clear
drush cache:rebuild
drush config:import
drush cache:rebuild
drush deploy:hook

Update the Database

Update hooks are run first to ensure that any manual updates to config values or table schemas are completed before subsequent steps are run.

Note that in Drupal 8 we should not be uninstalling/installing modules with update hooks, as was common in Drupal 7, this is now managed via the core.extension.yml configuration.

# Process any hook_update_N functions and apply
# database schema updates.
"$DRUSH" --root="$WEB_DIR" updatedb -y

Clear Caches

It is important to clear caches before importing configuration to ensure that the correct config settings are active if you are using config_filter or a module that implements a config_filter plugin (e.g. config_split). Other changes made by update hooks may also necessitate the need for caches to be cleared to ensure the site can be bootstrapped.

# Rebuild caches.
"$DRUSH" --root="$WEB_DIR" cache:rebuild

Import Configuration

With database updates made and caches cleared, we can now import the latest configuration.

Update We previously advocated for running this before database updates to ensure the following:

  1. Staged configuration changes are added to allow database updates to act upon content related to new configuration.
  2. Ensure update hooks that alter configuration are not overwritten by a configuration import. Note that this scenario will still cause problems on subsequent deploys if configuration is not in version control, but it is a “safer” method if it is forgotten the first time.

Some of these concerns are still valid, but this process is better suited for a local development workflow than a production deployment process.

# Import the latest configuration.
"$DRUSH" --root="$WEB_DIR" config:import -y

Clear Caches Part II

After the configuration is imported we clear caches again to ensure that everything is using the latest configuration values and settings.

# Rebuild caches.
"$DRUSH" --root="$WEB_DIR" cache:rebuild

Deploy Hooks

Some update hook processes may need to be run after the latest configuration is imported. We previously advocated for running config:import before updatedb for this very reason, but now there are dedicated hooks for each state.

As the drush deploy documentation page page explains:

  • HOOK_update_n() Low level changes.
  • HOOK_post_update_NAME() Runs before config is imported.
  • HOOK_deploy_NAME() Runs after config is imported.

Update Entity Definitions

In Drupal 8, entity updates allow for schema definition changes in the entity base tables to be updated. Running entity updates after database updates ensure that if a module alters entity tables via an update hook, the entity updates system will safely exit after discovering the schema change already exists on the table.

Note that while this is a valuable tool early in the development process, it should not be included in your final deployment script used on a production site due to possible data loss and its inability to update fields with existing data. Additional information on this can be found in this StackExchange thead.

Update: This command was removed from core in 8.7.0 as it was deemed "to be dangerous because it can lead to unforeseen side effects and critical bugs." However, the functionality will live on via the devel_entity_updates module if you want to leverage it for development purposes.

# Update entity definitions.
"$DRUSH" --root="$WEB_DIR" entity:updates -y

Cron

If you absolutely need to trigger cron as a part of your deployment process it would likely be executed here. However, we would strongly recommend decoupling your cron runs from deployments to avoid extended deployments due to long running cron processes.

# Run cron.
"$DRUSH" --root="$WEB_DIR" cron

The Ultimate Cron module will greatly aid in fine tuning your cron execution times and avoid triggering long-running queue processing during a deployment.

Clear Caches Part III

Many of the commands above clear caches, but there always seems to be an edge case that requires an additional cache clear. This optional final cache clear covers those cases.

# Rebuild caches.
"$DRUSH" --root="$WEB_DIR" cache:rebuild

Disable Maintenance Mode

Everything should be ready to go, so we can disable maintenance mode and get the site back online.

# Disable maintenance mode.
$DRUSH--root="$WEB_DIR" state:set system.maintenance_mode 0

Deploying Your Deployment

To standardize this process across the sites we manage, we have created an Ansible role that can be configured with a succinct playbook stored in the site’s repository. By centralizing the deployment process, we can make necessary changes in one place, version it, and roll it out to all of our sites with a one-line change to that site’s deployment playbook.

exit 0

Note that these are recommended deployment steps for a “regular” server stack. If you are using a cloud hosting provider you should always use the deployment instructions in their documentation.

You can now deploy with greater confidence and an improved understanding of the various actions required to deploy a Drupal site. Along with the knowledge of the proper order to execute them. There will invariably be fresh deployment challenges around the corner, and with a solid grasp of the what, and also the why, you are now better equipped to solve any challenges that come your way.