Automate Drupal site updates with a deployment module

Much of the work you do when building Drupal sites revolves around enabling and configuring modules, clicking various admin forms and updating settings. These settings are stored in the database. You should make changes to Drupal sites locally first and not directly on the live site. But changes you make to your local Drupal site need to be replicated on your live site. This tutorial is a walk through of how to automate these changes by storing them in code in a Site Deployment module. No more clicking and configuring multiple times!

Drupal deployment

Before we carry on, what exactly do we mean by deployment and why do we need to worry about it?

In its simplest terms, deployment is when you release changes to your website from your local environment to your live site. In professional environments, you are likely to have multiple stages before live, such as QA and staging. Each of these stages require deployment for each release.

Manually making configuration changes is a serious waste of valuable time and increases the risk of human error. The goal should always be to automate the deployment process as much as possible.

The Features module can take care of a lot of it, such as custom Content Types, Views, Image Presets, Context and Variables (using Strongarm). However, there are always other changes that are stored in the database that either can’t be exported into a Feature, or Features is just not appropriate. You still need a way to deploy these. You also need a way to control Features. You need to enable, revert and update Features.

Not sure how to use Features? Check out how to create your first Feature.

The deployment module

The concept of a deployment module is pretty simple. You have one custom module for your site which handles all of the deployment needs. Most, if not all, of the real work takes place in the .install file. In that file, you implement hook_update_N. Typically you would write one hook_update_N function per release. In that function, you write code to make changes to your Drupal site.

To execute changes in the Site Deployment module, all you need to do is ask Drupal to run its update. This can be achieved by going to update.php for your site, or running a drush command (drush updb). Every time you do a deployment, you can run Drupal update again, and your site will be up to date. All changes will be automatically executed.

Intro to hook_update_N

This hook is normally used for modules where a change to the database is required . When a user upgrades to a newer version of the module, updates should be run and every implementation of hook_update_N will be since the last one that was run.

Over time, you might end up adding more and more updates. In order for Drupal to know which updates have already been run, you need to add a number to the function name for each one. When each update is run, Drupal stores that number as the schema version in the system table for that module. Drupal runs updates in numerical order. Then when Drupal runs the next update, it will run the update(s) greater in number than the previous update that has run.

For the Site Deployment module, the number we will use is 7000. So the update function will be site_deployment_update_7000().

Why 7000? There is a pattern for the numbers. The first digit is the core version of Drupal. In this case, it is Drupal 7, so you need to use 7. The second digit is the major release version for the module. This module is still a dev version, so you can use 0. Eventually, it will become major release version 1, in which case use 1, making the updates 71xx. You may even have a second release, so in that case, the update numbers will be 72xx. The final two digits are for counting sequentially. You can start with 00. The next update will be 01 (7001) and then 02 (7002) and so on.

Makeup of update number:

Using hook_update_N is a very convenient way to store and execute changes for deployment because each site knows which update was run last. When you run update again, each site will automatically run all new updates and therefore all of the required deployment code.

Let’s look at a simple example - you have two versions of a Drupal site, local and live. You need to make some changes, so you implement hook_update_N. Since this is the first time you have done this, the number you use is 7000. You then deploy your Site Deployment module, and any other code changes, to your live site. You then run the Drupal updates either by going to update.php or running drush updb. This will then execute the update function with 7000.

The next time you want to make a change, you create a new function with the number 7001. You deploy the code to your live site and run the Drupal updates again. Drupal looks at the schema version in the system table for your Site Deployment module and sees that 7000 has run but 7001 has not. So it goes ahead and executes 7001.

Advantages of the Site Deployment Module

  • Because changes are in code and are not made manually in an admin form, it can be put under version control along with the rest of the code base.
  • Each site in your pipeline will “remember” the last deployment that was run on it by storing the last update increment in the system table. When you run Drupal Update on a each site, it will run all of the updates since the last one that was run.
  • By scripting all changes, you dramatically reduce the chances of human error. You do not need to replicate all that clicking and form saving. It also makes it much easier to test.
  • It will dramatically reduce the time it takes to make a deployment. Manually repeating all the manual changes on a production site on deployment day can take a substantial amount of time. Scripting all of it and running one command is insanely quick in comparison.
  • When working in a team environment, your fellow team members just need to do a git pull to get your code changes and then run the Drupal update. They will then get all of the database updates that you have made (and you will get theirs) without labour intensive manual updates or sharing databases locally.

Setting up the Site Deployment Module

Because this is a custom module, you can call it what ever you want. Common names are sitename_deployment or deploy_update. For this example, I am going to call it site_deployment.

Create your module with these three files:

  • site_deployment.module
  • site_deployment.install

The info file

The info file should contain the following:

  1. name = Site Deployment
  2. description = Deployment module, used to automated changes to this site
  3. core = 7.x
  4. version = "7.x-1.x-dev"

The module file

The module file doesn’t need to contain any working code. We will simply include a comment.

  1. <?php
  2. /**
  3.  * @file
  4.  * Module file for Site Deployment
  5.  */

The install file

In the install function, you can write an implementation of hook_update_N for each deployment. This is the heart of the site deployment module.

Let’s take a look at the code for the first function.

  1. <?php
  2. /**
  3.  * @file
  4.  * Install file for Deploy Update
  5.  */
  7. /**
  8.  * Deployment function for 1st deployment
  9.  */
  10. function site_deployment_update_7000() {
  12. }

So what goes in each function? Let’s look at some examples of four different deployments.

Deployment One

Let’s say that with the first deployment, you want to enable the Contact and Blocks modules and disable the Devel and Coder modules.

You just need to add three lines of code to do that:

  1. /**
  2.  * 1st Deployment: enable contact, book; disable devel, coder; enable garland;
  3.  */
  4. function site_deployment_update_7000() {
  5. module_enable(array('contact', 'book'));
  6. module_disable(array('devel', 'coder'));
  7. theme_enable(array('garland'));
  8. }

The first calls the core module_enable function, which takes an array of modules that you want to enable. In this case, the modules to enable are Contact and Book. The second calls the core module_disable function, which takes an array of modules you want to disable. In this case, the modules to disable are Devel and Coder. And the third line calls the core theme_enable function, which takes a list of themes you want to enable. In this case, the theme to enable is Garland.

Deployment two

In the second deployment, you want to do two things: change the settings for a block and change the path for the site frontpage.

  1. /**
  2.  * 2nd Deployment: update system navigation block; set new path for frontpage;
  3.  */
  4. function site_deployment_update_7001() {
  5. db_update('block')
  6. ->fields(array(
  7. 'region' => 'sidebar_second',
  8. )
  9. )
  10. ->condition('module', 'system')
  11. ->condition('delta', 'navigation')
  12. ->execute();
  14. variable_set('site_frontpage', 'node/1');
  15. }

The first chunk of code is a db_update to change the navigation block that comes from the system module and set its region to sidebar_second.

Before the change, the navigation block was in sidebar_left:

After update is run, the navigation block is in sidebar_second:

The final line of code changes the path of the default frontpage. You would normally do this in the site information page. This then saves it to the variable table. You can bypass using the site information form and set the variable directly in code. To do this, use the variable_set() function. This takes two arguments, the name of the variable and the value you want to set for that variable.

The variable for the frontage is site_frontpage. This update sets the site_frontpage to node/1.

Before the change, the site frontpage field is blank:

After the update is run, the site frontpage field is populated with the new path, node/1:

Deployment Three

In the third deployment, we add a new link to the main menu.

  1. /**
  2.  * 3rd Deployment: Add Register link in the main menu
  3.  */
  4. function site_deployment_update_7002() {
  5. $item = array (
  6. 'link_title' => 'Register',
  7. 'link_path' => 'user/register',
  8. 'menu_name' => 'main-menu',
  9. 'weight' => 0,
  10. 'plid' => 0,
  11. 'weight' => 11
  12. );
  13. menu_link_save($item);
  14. }

This will add a new menu item with a title of Register. It will link to the detail user registration path and will be added to the Main Menu.

Deployment Four

In the fourth deployment, we want to revert a feature that has become overridden. Features become overridden when one of the settings changes in the database and it the Feature code has not been updated. You will want to revert it when you want to remove the database change and revert it back to what is stored in code.

  1. /**
  2.  * 4th Deployment: Revert News feature
  3.  */
  4. function site_deployment_update_7003() {
  5. features_revert(array('news' => array('field')));
  6. }

To revert a feature, you call the features_revert function. It takes a multidimensional array as its argument. This array contains the feature and components to revert. In this case, we are reverting the field component of the news feature.

Wrapping Up

Automating all changes for a Drupal site is an essential element in creating robust, risk free and testable deployments. In this introduction, we have looked how to set up your own site deployment module and some examples of this in action.

Example Module on

I have set up an example module on This contains the code outlined above. Over time, I will add more examples which you can use for inspiration.

Further Information

If you would like more information on using the Features module, check out my book Master Drupal Module Development, which includes two chapters on using the Features module.


That was a great intro, many thanks for taking the time to write it up.

Blair Wadman's picture

You're welcome Andy

Many thanks! Your explanation is so simple and clear! I really appreciate that every detail/step is fully and simply explained, so as to leave no doubt.

Blair Wadman's picture

Glad you enjoyed it!

I appreciate what you're doing here but I feel on a large site, the amount of config you'd be putting into this module would be significant and an incredible amount of work. At the moment, at the place I work, we're using a mixture of features and manually reproducing the config changes on the live server. It's not ideal either. I wonder if your deployment module example might be the basis of a simplified but code-based deployment module?

Blair Wadman's picture

In the past I have worked in teams that also used a mixture of Features and manually reproducing config changes as well. Once you get used to doing all changes in code using a site deployment module, you won't want to go back (or at least I don't!). The sites I work on are large sites also. One of the biggest gains is reducing bugs with a team of devs. You can do a git pull and Drupal update to get other developers changes daily (or throughout the day) without having to run through the manual changes each time. If every developer has to run through manual changes that other developers are creating, they probably won't do it that often.

Thanks for this nice article.
Just one question:
when or why is Features module in not appropriate? In what scenario Features could not finish the task?

Blair Wadman's picture

It does depend on how you are using Features in the first place. In all of the large scale Drupal projects I have been involved with, Features is used to export and manage updates through the deployment pipeline. At each stage, Features are always reverted after the code deployed. Because the Feature code has the latest changes, reverting all Features will change the database settings to what is stored in code. The word "revert" is confusing and counter intuitive in this case. The problem starts when editors or site managers update settings on the live site. If those settings are stored in a Feature, when a new deployment happens, the Feature will be reverted and the changes will be reverted and lost. So a choice needs to be made at the start - should a setting be changeable in live itself or is it always controlled by developers? If it should be changeable on live, it is more appropriate to deploy the changing using a Site Deployment module. In this case, the settings is a one time change and site editors are free to change the setting. If it is always controlled by developers, it is more appropriate to export and deploy using Features.

There are also cases where a particular setting can't be exported in a Feature but that is less common scenario these days.

Interesting, like the feature revert being used in code ;)

Blair Wadman's picture

Thanks Farhan!

I support and actively advocate "site deployment" modules. I think this article is headed in the right direction, but there is enough flaws in it that I wouldn't recommend people follow this it. Below I've outlined the big problems, as I see them.

Calling the module site_deployment in every site suggests that it is the same module in each site. These deployment module are unique per site and should be named as such. If the client is called "Cheap and Nasty Pizza", then namespace the module with a prefix cnp_, just like you should for all client specific custom modules or features.

The hook_update docblocks should not contain "Implements hook_update_N()." as that is what drush will display when running "drush updatedb". Instead you should be using a short description of the change "Enabling the nose picker module." The "nth Deployment: " is just additional unnecessary clutter.

Friends don't let friends use core blocks. Blocks should be handled with the boxes module if you want them as config, or bean if you want them as entities - each approach has their pros and cons. Either way blocks should be placed using context or panels and be exported to features. That way your block config is always properly documented in code. With the approach implemented above you have no way of knowing if someone has moved a block.

Features reverts should be a normal part of your deployment process, not a step in a hook_update. You should always run "drush fra -y" before and after running "drush updatedb" to ensure all of your features are in a default state.

Blair Wadman's picture

Thanks for your comment. Some points:

1. I actually said sitename_deployment is a common name (where sitename is replaced with the actual site name) and that site_deployment is used for this example. I’m not saying that the exact name “site_deployment" should be used in real life.

2. Fair point. I’m used to writing “Implements [hookname]” because that is the Drupal standard, but I realise hook_update_N is the exception to that rule. Have updated the post accordingly.

3. I always recommend using context or panels to place blocks and export in features. But what if you are on an existing project (you didn’t build the site) that actually has blocks placed “old school” and you have a task to move one of the blocks? You could suggest changing all of the blocks to use context, but what if the client/boss says no, just move the block? This is just an pragmatic example of the sort of thing you can do if you need to, not an article about the benefits of using context or panels etc.

4. In an ideal world, you should have drush fra -y scripted in a deployment/rebuild script so you be sure it is always run (along with drush updb etc). But this is an intro piece and people might not have that in place yet. This example is for where you want to revert a particular feature (there are projects where features revert all is not run for a variety of reasons).

Nice article Blair, I've just used the exact same approach on a large scale Drupal project hosted on AWS with a large team, and the process works perfectly.

We have a deployment script as part of a CI pipeline which runs drush fra, and drush updb after a code release.

Thanks for giving a great overview, helped me a lot to understand how my future deployment will work out better.

Is there some kind of documentation on the functions and array syntax to use for the various kinds of configuration one might want to change? E.g. how do I know, when/how to use db_update() or if there's a dedicated function for the desired functionality. (Am I right thinking that db_update could also be used to enable/disable modules?).

Further questions I have no idea (yet): how do I remove a field from a content type? How do I set a field formatter for a certain display of a content type?

Thanks again,

Blair Wadman's picture

db_update() is only used for a SQL query to update the database. There are often dedicated functions that you can use instead. E.g to enable a module, you can use module_enable(array('module_name')), so you don't need to query the database directly with db_update.

For removing content type fields and field formatters, it is best to use Features for that. You can add your content type and fields to a Feature and then remove them at a later date if you wish.

Hello! This was just what I needed!
I just started working on a startup company and I'm responsible of rebuilding the web service on Drupal. This time everything have to be rock solid. So this article really provides useful info for me.

Have you tried or now something about the 'deploy' module for Drupal?

Seem to be quite popular.

Blair Wadman's picture

Glad you liked it!

I've used Deploy on past projects, but that is for deploying and staging content rather than making the kind of changes outlined in this post.

Nice Article to start with deployment process!! Let me read your books as well!! Thanks

Great article Blair!

I am trying to improve my Drupal DevOps and this is a huge help. But how to you handle dependencies? Do you use a make file or do you add them to your info file?

How do you sort out all the different updates that are different in each and every branch to merge to develop?
4 developers working on 4 separate areas of the site in 4 separate branches led to 4 different hook_update_7001's.
Having them use different numbers didn't work cause the guy using 7004 had his database set at 7004 so the updates 7001-3 never ran on his copy when that code was merged back into his site.

Blair Wadman's picture

If you have different branches, then I don't think you have much choice but to stick to the normal numbering and then resolve the code conflicts when they arise and adjust the numbers accordingly.

So if two devs add hook_update_7001, then when the second merges there will be a conflict. At that time, he/she will need to resolve the conflict and change his/her update to hook_update_7002.

Very helpful / thanks, but could you say a little more about the relationship between the naming of the update function and the VERSION entry in the .info file? In your example, you've set VERSION to "7.x-1.x-dev", but are using a zero in the update function (e.g., "site_deployment_update_7000"). What does the first zero in 7000 correspond to? Not understanding this, it seems like it should be 7100, corresponding to "1.x", but, then, I don't understand this yet, so... Thanks again!

Blair Wadman's picture

Because it the module is a dev version (7.x-1.x-dev), I'm leaving it as 7000. But if it wasn't a dev version, and the version was 7.x-1.x then it should be 7100. And if the module has a version 2 (7.x-2.x) then it would be 7200.

Awesome post. What's the method of reverting changes using the module you used.

Hi Blair,

Love this post and wish I had this when I was learning about module install files.

I have the same question as the last post about reverting changes. These functions are only run once and then you have to write a new function to rerun any functionality in the hooks. Either you can add another update function or go into the database and manually change the update version to before the one that you need to run. Both of these options are not ideal.

How can this be more aligned with the features style where you have configuration that can be reimplemented with a click? Perhaps we are stuck with this solution for now or have to create a custom module with functionality outside the install file.

Re: Reverting changes (Anonymous).

Not sure if you mean reverting changes made by your update function or reapplying the changes made by the update function. If the former then it's hard to revert changes without storing the info somewhere. You'd have to integrate that into the update function and store the previous settings/data somewhere. Then you can grab that info when you wish to revert it. If the latter then you have the 2 options I mentioned above, making a new update function with the same code as your previous update function or reset the version of the update hook for the particular module in the database ('system' table 'schema_version' column) and then run update.php. Please know what you are doing if you want to reset the schema_version especially if there are update functions other update functions numbered after the one you want to run.

Thanks again for the great article!

Blair Wadman's picture

Hi Harp,

If I need to revert changes, then I go down the route of creating a new update function. There isn't really an easier way that I know of. Changing the update number is problematic and error prone.

One tip if you know you are going to have to re-run an update in the future is to wrap it in a separate function and call that function from your update hooks. This can make it much cleaner. For example:

  1. site_deployment_update_7001() {
  2. site_deployment_dosomething();
  3. }
  5. // .. more update hooks
  8. site_deployment_update_7008() {
  9. site_deployment_dosomething();
  10. }
  12. site_deployment_dosomething() {
  13. // code to do something in more than one update hook
  14. }

Hi Blair,

Thanks for such a nice article, I have done my first deployment module using same example and it works perfectly.

I have one query, Can I use deployment module for the cross database connections too? If yes, can you provide an example code for that.

Thanks in advance.

Blair Wadman's picture

Glad you enjoyed it!

I haven't tried using a deployment module for cross database connections, so I can't comment on its viability. Normally you can query to a different DB by either of these methods:
1) db_set_active - more info here: &

2) Database::getConnection - more info here:

Thanks Blair,

I tried it with first method and it works. :)
I have used db_set_active function to query with other DB but was not sure whether it works in Deployment Module, but it really works.

Thanks again

Blair Wadman's picture

Great stuff, glad it works. Thanks for reporting back!

Super article. Many thanks.

Hi Blair, Thanks for the all the examples and explanation.
This helper module for D7 takes your deploy strategy one step farther and provides both feedback and the possibility of failing an update if what was attempted, did not work.

New comments for this tutorial have been turned off.