We’ve been using Team City for a while now to deploy our ASP.NET based applications to our development, test, and production environments. Up until recently we had been using a combination of MSBuild, MSDeploy, and various scripting languages for this process. These scripting languages included cmd, powershell, and rake. The scripts would use the artifacts of our main CI build, perform the appropriate config transformation, and then deployment to the target hosted environment. The good thing about this approach was that it allowed us to perform a single build and use those same artifacts for deployment to multiple environments.
Even though this approach was working and reliable it did have some significant drawbacks, so we set out to see if there were any changes we could make to improve our process…
Here are some of the drawbacks we found with our existing deployment strategy:
- No automated packaging – To create our deployment artifacts, we were performing a full rebuild with MSBuild without any type of publishing or packaging. This meant we didn’t have MSBuild packaging our application removing files that were unnecessary for deployment to IIS. Instead we had compiled version of all the artifacts of the project. As part of the deployment script, the unneeded files were either manually removed (think delete statements) or actually deployed to the target environment.
- Large number of scripts – Creating the numerous rake, powershell, and command scripts was time consuming and difficult to reuse across projects. This resulted in a large number of very specific deployment scripts. Since the scripts were specific to the environment, the number of scripts was equal to the number of deployable applications * the number of environments. In some cases this could become quite a large number of scripts to maintain (> 12).
- Limited reusability – When developers on other teams wanted to setup similar deployment scenarios, there was not an easy way for them to benefit from the work that had already been done. The barrier to entry on configuration of deployments was very high. By nature of our customers, our team ends up supporting a large number of small projects. When it proved difficult to setup and configure continuous integration and deployment, it simply wasn’t done. Making this a less complex activity would go a long way towards improving these processes for our entire team.
- Out of band configuration transformations – Configuration transformations were not being done in-band with the config files themselves. Although Visual Studio provides a very convenient mechanism (config transformations) for managing the base configuration file and the modifications to this file for each environment, we weren’t using this feature. Instead our deployment script files had these configuration changes hard-coded in to them. Storing configuration transformations out-of-band from the main solution made it more difficult to keep up with changes. It also made deployments more brittle when new configuration settings or files were added.
We solved these problems by breaking the workflow up into four distinct steps:
- Build – Using MSBuild to do a full rebuild of the application in the desired deployment configuration.
- Publish / Package – Using MSBuild to publish only the files necessary for a deployment to a package.
- Config Transformation – Using MSBuild to perform a web.config transformation and apply the correct settings for the targeted environment.
- Deploy – Synchronize the deployment environment with the final package (post xml transformation) using MSDeploy.
Preparing the Build Server
In order for us to perform the packaging and config transformation with MSBuild, we needed to have the Visual Studio assemblies that contain the bits for doing performing these tasks available on the Team City build server. The easiest way to accomplish this is to copy these assemblies and supporting files to the same directory structure on the build server.
It’s not necessary to do a full install of Visual Studio on the build server, but instead you can just copy the files from a local Visual Studio installation in these folders to the same directory on the build server:
This allowed us to use MSBuild to do the same packaging and xml transformations on the Team City build server as we are doing on our development workstations.
It’s also important to note that there are very good reasons not to install Visual Studio on the build server. In general you want your build environment to be as close (in configuration) to the deployment target as possible. This allows you to build, test, and verify your applications using continuous integration in an environment that has the same configuration as your deployment target environments. This makes it more likely that you will catch any potential issues with your application *before* deploying to the target environment.