Deploying Drupal sites involves database updates, configuration imports, cache rebuilds, and custom hooks — steps that are easy to forget or get wrong when done manually. Drush automates all of this into a single command, and when paired with DeployHQ, your entire Drupal deployment becomes a push-to-deploy workflow.
This guide covers how drush deploy works, how to write custom deploy hooks, and how to set up fully automated Drupal deployments with DeployHQ.
What Drush Deploy Does
The drush deploy command runs a sequence of operations in the correct order every time:
drush updatedb— runs pending database updates to match the deployed codedrush config:import— imports configuration changes from your codebase into the databasedrush cache:rebuild— clears and rebuilds all cachesdrush deploy:hook— executes any custom deploy hooks you've defined
Without Drush, you'd run each of these manually (and risk forgetting one). With drush deploy, the entire post-deployment sequence is a single, repeatable command.
Essential Drush Commands for Deployments
Beyond drush deploy, here are the commands you'll use most when managing Drupal:
| Command | What It Does |
|---|---|
drush deploy |
Runs the full deployment sequence (updatedb → config:import → cache:rebuild → deploy:hook) |
drush updatedb |
Applies pending database schema updates |
drush config:import |
Imports configuration YAML files into the active database |
drush config:export |
Exports active configuration to YAML files |
drush cache:rebuild |
Clears and rebuilds all Drupal caches |
drush deploy:hook |
Runs custom deploy hook implementations |
drush status |
Shows Drupal installation status and environment info |
drush pm:list |
Lists all installed modules with their status |
drush cron |
Runs Drupal's cron tasks |
drush watchdog:show |
Displays recent log messages |
drush sql:dump |
Exports the database as SQL |
drush state:set system.maintenance_mode 1 |
Enables maintenance mode |
Writing Custom Deploy Hooks
Deploy hooks run automatically as the last step of drush deploy. They're useful for one-time data migrations, content updates, or any task that needs to happen exactly once after a deployment.
Creating a Deploy Hook
Deploy hooks are defined in your module's .deploy.php file. Each hook is numbered — Drush tracks which hooks have already run and only executes new ones.
Create my_module.deploy.php in your module's root directory:
<?php
/**
* @file
* Deploy hooks for my_module.
*/
/**
* Migrate legacy user roles to new permission structure.
*/
function my_module_deploy_10001(array &$sandbox): string {
// First run — initialise the batch.
if (!isset($sandbox['total'])) {
$sandbox['total'] = \Drupal::entityQuery('user')
->condition('roles', 'legacy_editor')
->count()
->accessCheck(FALSE)
->execute();
$sandbox['current'] = 0;
}
// Process 50 users per batch.
$uids = \Drupal::entityQuery('user')
->condition('roles', 'legacy_editor')
->range(0, 50)
->accessCheck(FALSE)
->execute();
foreach ($uids as $uid) {
$user = \Drupal\user\Entity\User::load($uid);
$user->removeRole('legacy_editor');
$user->addRole('content_editor');
$user->save();
$sandbox['current']++;
}
// Tell Drush whether we're done.
$sandbox['#finished'] = $sandbox['total'] > 0
? $sandbox['current'] / $sandbox['total']
: 1;
return "Migrated {$sandbox['current']}/{$sandbox['total']} users.";
}
/**
* Set default value for new site configuration.
*/
function my_module_deploy_10002(): string {
\Drupal::configFactory()
->getEditable('my_module.settings')
->set('enable_new_feature', TRUE)
->save();
return 'Enabled new feature flag.';
}
How Deploy Hook Numbering Works
- Hooks are named
{module}_deploy_{number} - Drush stores which numbers have been executed in a key-value store
- Only hooks with numbers higher than the last executed number will run
- Use a numbering scheme like
10001,10002, etc. to leave room for ordering
When to Use Deploy Hooks vs. Update Hooks
Use Deploy Hooks (deploy.php) |
Use Update Hooks (install) |
|---|---|
| Content or data migrations | Database schema changes |
| One-time configuration changes | Module install/uninstall logic |
| Post-deployment cleanup tasks | Changes that need to run before config import |
The key difference: deploy hooks run after config:import, while update hooks run before it (during updatedb).
Manual Deployment Script
If you're deploying Drupal to a server with SSH access, the basic workflow is:
# Pull latest code
cd /var/www/drupal
git pull origin main
# Install/update dependencies
composer install --no-dev --no-progress --optimize-autoloader
# Run the full deploy sequence
drush deploy
# Verify the site is healthy
drush status
drush watchdog:show --count=5
This works, but you're running it manually every time. Let's automate it with DeployHQ.
Automating Drupal Deployments with DeployHQ
DeployHQ connects your Git repository to your server and handles the full deployment lifecycle — file transfer, build steps, and post-deploy commands — every time you push code.
Step 1: Create a Project
Sign up at deployhq.com and create a new project. Connect your GitHub, GitLab, or Bitbucket repository where your Drupal codebase lives.
Step 2: Add Your Server
Go to Servers → New Server and configure:
- Name: Production (or Staging, etc.)
- Protocol: SSH/SFTP
- Hostname: Your server's IP or domain
- Username:
deployhq(a dedicated deploy user) - Deployment Path:
/var/www/drupal - Zero-downtime deployments: Enabled
With zero-downtime deployments enabled, DeployHQ deploys to a new release directory and atomically switches a symlink when everything is ready. Your site stays live throughout the deploy — no downtime, no broken requests.
Note: With zero-downtime enabled, your files will be served from /var/www/drupal/current. Update your web server configuration to point the document root to this path.
Setting Up SSH Key Authentication
On your server, set up the deployhq user:
# Create the deploy user
sudo adduser --disabled-password deployhq
# Set up SSH directory
sudo -u deployhq mkdir -p ~/.ssh
sudo -u deployhq chmod 700 ~/.ssh
# Add DeployHQ's public key (shown in the DeployHQ server setup screen)
sudo -u deployhq nano ~/.ssh/authorized_keys
# Paste the key, save, and exit
sudo -u deployhq chmod 600 ~/.ssh/authorized_keys
Make sure the deployhq user has write access to the deployment path and can run drush and composer.
Step 3: Configure the Build Pipeline
Go to Build Pipeline → New Command. DeployHQ's build pipeline runs commands on DeployHQ's servers before transferring files, so you don't need build tools installed on your production server.
Add a Composer build command:
composer install --no-dev --no-progress --optimize-autoloader
Enable Stop deployment if this command fails — if composer install fails, there's no point deploying broken code.
Speed up builds with caching: Go to Build Configuration → Cached Files and add:
vendor/**
This caches your vendor directory between deployments so Composer doesn't re-download every package on each deploy.
Step 4: Add Post-Deploy SSH Commands
Go to SSH Commands and add a command that runs after files are transferred:
cd /var/www/drupal/current && drush deploy
This runs the full Drush deployment sequence (database updates → config import → cache rebuild → deploy hooks) on your server after DeployHQ has deployed the new code.
You can also add a health check after the deploy:
cd /var/www/drupal/current && drush status --field=bootstrap | grep -q "Successful" && echo "Deploy OK" || echo "Deploy FAILED"
Step 5: Enable Automatic Deployments
In your project settings, enable automatic deployments. Now every push to your main branch triggers a deployment automatically:
git push → DeployHQ detects change → Runs composer install → Transfers files → Runs drush deploy → Done
No manual steps. No SSH-ing into the server. Push your code and DeployHQ handles the rest.
Step 6: Use Config Files for Per-Environment Settings
If you deploy to multiple environments (staging, production), use DeployHQ's config files to inject environment-specific settings. For example, inject a sites/default/settings.local.php with different database credentials per server — without committing secrets to your repository.
Customising the Deploy Process
Maintenance Mode During Deploys
For major updates that change database schemas, you may want to enable maintenance mode:
cd /var/www/drupal/current && \
drush state:set system.maintenance_mode 1 && \
drush deploy && \
drush state:set system.maintenance_mode 0
Add this as your SSH command in DeployHQ instead of the plain drush deploy.
Customising Drush Subcommands
You can modify how drush deploy behaves by adjusting the configuration for its subcommands. For example, to skip certain config during import, add to your drush/drush.yml:
command:
config:
import:
options:
partial: true
updatedb:
options:
cache-clear: false
Rolling Back a Failed Deployment
If something goes wrong, DeployHQ makes rollback straightforward:
- One-click rollback: In DeployHQ, go to your deployment history and click Rollback on any previous deployment. DeployHQ re-deploys the exact state of that revision.
- Zero-downtime rollback: If you're using zero-downtime deployments, DeployHQ keeps previous release directories. Rolling back switches the symlink back to the previous release — instant, with no downtime.
- Database rollback: If the deployment included database changes, you may need to restore from a database backup. Consider running a database snapshot before each deploy (see our guide to server backups with AWS S3).
Common Questions
Can I run Drush commands without SSH access?
No. Drush is a command-line tool that requires direct server access. DeployHQ runs Drush commands over SSH as part of the post-deploy step, so you need SSH access configured on your server.
Will my site go down during deployments?
Not with DeployHQ's zero-downtime deployments. DeployHQ deploys to a new release directory and only switches the symlink after all files are in place and post-deploy commands have succeeded. Your visitors see zero interruption.
How do I deploy to multiple environments?
Create separate servers in DeployHQ for each environment (staging, production). Map different Git branches to each server — for example, develop deploys to staging and main deploys to production. Use config files to inject environment-specific database credentials and settings.
What if drush deploy fails mid-way?
If drush deploy fails (for example, a deploy hook throws an error), DeployHQ reports the failure in its dashboard. With zero-downtime deployments, the symlink hasn't switched yet, so your production site is still running the previous release. Fix the issue, push again, and DeployHQ will retry.
What version of Drush do I need?
The drush deploy command is available in Drush 10.3+ and is fully supported in Drush 12 and 13. If you're on an older version, update via Composer:
composer require drush/drush:^13.0
The Bottom Line
Drush turns complex Drupal deployment steps into a single command, and DeployHQ turns that command into an automated, push-to-deploy workflow. Together, you get reliable, repeatable Drupal deployments with zero downtime — no manual SSH sessions, no forgotten steps.
Ready to automate your Drupal deployments? Start your free DeployHQ trial — no credit card required.