EPiServer – Continuous Integration and Deployment
Two weeks ago I went to the EPiServer Meetup where Bartek Tatkowski talked about EPiServer and CI. This got me inspired to write about how we do CI and deployment in my current project. The site we are building is an EPiServer 6 CMS Enterprise site based on .NET 4.0 and ASP.NET MVC 2.0 (I blogged about it back in April). When we started the project we had very clear goals regarding CI and deployment:
- The build process should be totally automated
- Building the site should result in a release package
- The build server should run unit tests, code analysis and test code coverage every time the build is run
- The deployment process should be totally automatic (for the test environment and for the production environment)
- It should always be possible to rollback an update (files, database, IIS-settings, etc.). Rollback should also be fully automated.
I am very happy to say that we have fulfilled all our above goals, let me tell you have we did it.
Build process and the release package
We are using TeamCity as build server. TeamCity is great build server which support numerous different build agents and it hooks up very easily with our Mercurial version control system. This is what a typical check in/build/package scenario looks like:
- 1. A developer checks in new code
- 2. TeamCity picks up the change from Mercurial and starts a build
- 3. Once built TeamCity runs all the units tests, code analysis and code coverage
- 4. If the unit tests are successful the TeamCity creates a deploy package. The package contains a MSDeploy zip file, configuration files and our deploy tool chain (Powershell scripts and a few executables) – everything you need to deploy the site.
By downloading and extracting this file you get a file structure that looks like this:

Scripts – contains various scripts needed to perform deployment, the most important being Deploy.ps1
and Deploy-Local.ps1
.
SiteConfig – contains shared settings and one directory per unique environment (AT = Acceptance Test, ST = System Test, Dev-PM = My development environment). In the SiteConfig directory we also find the following files:
- settings.xml
- episerver-config.master.xml
- episerver-config-site1.xml
- episerver-config-site2.xml
The settings.xml
is the base of all non-episerver settings. It contains connection strings to databases, paths to where the web site should be installed, where logs should be placed, where VPP files are placed, integration settings to external systems and much more.
In each environment specific directory, such as “AT” it is possible to override these settings by creating a settings.xml
. The new settings.xml
does not replace the one in the parent directory, it is merged with it and the environment specific file takes precedence over the general.
A simple example let pretend this is our \SiteSettings\settings.xml
:
<settings>
<webDir>D:\WebSites\</webDir>
<logDir>D:\Logs</logDir>
</settings>
Say that I would like to override where the logs are stored in my ST environment. To do this I would create \SiteSettings\ST\settings.xml
, containing:
<settings>
<logDir>C:\Temp\Logs</logDir>
</settings>
The merged config would be:
<settings>
<webDir>D:\WebSites\</webDir>
<logDir>C:\Temp\Logs</logDir>
</settings>
We use this for both development and deployment. When a developer checks out the source code, there is a “Init.ps1
” script in the Scripts folder (not included in the release package). This script will setup the website according to the specified settings (in my case the Dev-PM folder). The script configures the local IIS server, creates app pools, creates the sites, sets permissions on folders, creates local MSMQ queues, configures NServiceBus and so on. Thanks to this script all the developers have a development environment that is nearly identical! Super sweet!
Ok, back to deployment. As you might have figured out, the episerver-config.master.xml
serves as base for all episerver sites. Since this is an enterprise installation we can have several sites and each –siteX represents such a site.
All the settings in the files are overridable in the same way as with settings.xml
. We use this to, for example, provide the appropriate license file for the specific environment.
SiteWebDeployPackage
This might be the least exciting directory, it simply contains a MS WebDeploy package created by MSBuild. This package contains everything needed to run the sites, except for the site specific configuration files that Deploy.ps1/Deploy-Local.ps1
will put in place.
Tools
This directory contains our “deploy tool chain”. The tool chain includes, MSDeploy.exe (WebDeploy), SimpleScriptRunner.exe (DB migration), 7za.exe (zip) and a bunch of other tools needed to setup and install Msmq and NserviceBus.
The deploy process
When we started implementing the automated deployment we were hoping to be able to do most of the heavy lifting in WebDeploy. But after a lot of research and testing we came to the conslusion that it was probably possible to do what we wanted to do in WebDeploy (WebDeploy is very powerfull), but we could not figure out how. The WebDeploy documentation at that time was almost non-existent and had few advanced examples. Instead we made the decision to use PowerShell for most of the work and use WebDeploy only as a method to package transfer files.
This is the steps needed to perform a deployment:
- 1. Download the latest release package from TeamCity
- 2. Extract it, start PowerShell and cd into the Scripts folder
- 3. Run “Deploy.ps1 <environment>”. The addresses of the servers are confgured in the environment settings
This is what Deploy.ps1 does:
- 1. Remotes to the service bus host and takes it offline
- 2. Remotes to the webserver and takes the site offline
- 3. Creates a backup of the entire IIS containing all its settings and sites using WebDeploy (used if rollback is carried out)
- 4. Creates a backup of all databases (used for rollback)
- 5. Uses SimpleScriptRunner to connect to our database server and apply database schema updates on our databases (not the EPiServer database)
- 6. Copies the deploy package to the webserver
- 7. Remotes to the webserver and runs
Deploy-Local.ps1
. The script installs the files, configures IIS, copies license files, etc.
- 8. Starts the site and the service bus and runs runs integration tests
- 9. Print result and exit!
If the update of some reason is not working as it should, the update can be easily rolled back by executing “Deploy.ps1 <environment> -Rollback”. This will restore all affected system to exactly the same point as they were before the update.
Conclusion
Automating the build and the deployment process is absolutly worth the effort it takes to implement it. By automating the process we have been able to catch more bugs, develop and release faster and at the same time make the entire process less dependent on certain people in the group – our knowledge is documented in the build and deployment scripts.
A very nice bonus from this is that we can version control everything related to the site, IIS settings, EPiServer settings, EPiServer Licenses and much more. Also, installing the release on a “empty” server is as easy as updating an existing installation.
The tools that made all this possible:
Microsoft WebDeploy – Deployment tool from Microsoft.
Psake– Build automation tool written in PowerShell (inspired by rake in Ruby).
SimpleScriptRunner – Tool to run a directory of numbered sql scripts against an MSSQL database.
OpenDBDiff – Tool/library to generate SQL-diff scripts.
PageTypeBuilder – Automatic synchronization of pagetypes in EPiServer (page types are defined in code)
Very nice writeup!
One way to handle license files for devs which is one of the few things I think Bartek’s solution didn’t handle is to have separate directories for each computer from which a build script copies from.
A couple of posts (blatant self promotion) on that topic:
http://joelabrahamsson.com/entry/manage-multiple-webconfig-files-using-phantom
http://joelabrahamsson.com/entry/handling-multiple-aspnet-configuration-files-with-build-events
Very nice explanation. I am newbie at EpiServer world, and now have task to build CI process for it. Code deliveries are easily performed, but have you ever thought on content deliveries? E.g., some example content from development environment to QA and UAT?
While it’s a great post, the design of EPiServer itself screams of ego. I’m sorry, Umbraco can web deploy itself without installation and without all that loose file dependency. EPiServer needs to do a lot better.
It’s only a CMS. Not god damn SharePoint (which is still unacceptable.)
Great great work from you guys tho. Thanks all the same.