Artifact Promotion Deployments: Sitecore Built in the Cloud

March 28, 2017

Blog | Development | Artifact Promotion Deployments: Sitecore Built in the Cloud

To start off I am guessing some of you reading this are wondering what exactly the headline means so I will ask you to set your current Sitecore build, deployment and branching strategy aside and try to visualize a workflow where all development gets merged into one branch, and all builds go thru Dev before ever getting to a QA or Production server.

I assume you are as skeptical as I was about this (I thought this was complete and utter heresy). It took me actually implementing this approach before I was on board and I’ll do my best to illustrate it to you in this post.

The Plan

Here’s a breakdown of what the project I use throughout this post consists of:

  • A standard Sitecore 8.2 install
  • A Visual Studio Online Project, for our example we are going to use TFS-git for our source control
  • 3 TDS projects (could use Unicorn as well, but for sake of argument, we are using TDS) to manage our Core changes, test Content, and things we will want to always Deploy
  • A web project (with our custom layout changes) with Sitecore.Ship installed

Below is a high level breakdown of what’s in this post:

The Project

Now that you have the high-level project overview, let’s go a little bit into the actual Visual Studio Solution.

You can see in the Solution Explorer screenshot, the Feydra solution (yes, it’s named after the very nifty front-end productivity tool that I will blog about at a later date) consists of the three TDS projects, and one Feydra.Web project.

For our local Debug target we set up the TDS.Deploy project to do the local file deployments (unchecking Disable File Deployment and pointing the deploy folder to our local Sitecore webroot), but on our VSTS build we will want something completely different. Assuming you have all the necessary projects in your solution, create a new build configuration called VSTSBuild. Now, switch the active configuration to VSTSBuild and open up the properties of all the TDS projects.

In here, there should be no values for any of the projects under the Build tab, unlike the Debug configuration. Instead, we will setup each one of these to create an Update Package. On the Update Package tab, check the Generate package during build, choose Generate only an item package for the Package Generation Options, and update Sitecore Assembly Path to point to your /src/lib/Sitecore directory (I usually grab the dlls I will need from my webroot/bin and drop them here). I believe the Update Package requires at least the Sitecore.Zip, Sitecore.Update, Sitecore.Kernel, and Sitecore.Logging dlls. Your Update Package tab should look like:update package tab

Now, if you have updated all of the project properties correctly, you should be able to build the entire solution under the VSTSBuild configuration and see a TDS.Content/bin/Package_VSTSBuild/TDS.Content.update file.


The next big piece to this is getting TDS to work in a cloud environment.

  • Open a file explorer, grab all files from C:\Program Files (x86)\MSBuild\HedgehogDevelopment\SitecoreProject\v9.0, and drop them into a src/lib/tds/ directory (or something similar).
  • Open the HedgehogDevelopment.SitecoreProjects.targets file, and update all AssemblyFile="HedgehogDevelopment.SitecoreProject.Tasks.dll" to AssemblyFile="$(MSBuildThisFileDirectory)HedgehogDevelopment.SitecoreProject.Tasks.dll" (should be lines 2-26). Then update one more line, TDSTaskAssemblyPath="HedgehogDevelopment.SitecoreProject.Tasks.dll" to TDSTaskAssemblyPath="$(MSBuildThisFileDirectory)HedgehogDevelopment.SitecoreProject.Tasks.dll" and save.
  • Add a TDSLicence.config file to that same directory (and yes, use that specific spelling). Inside this config, put <license Owner="__tdsOwner__" Key="__tdsKey__" /> and save.

Ship Utils

The Powershell scripts will make sense later when we get to deploying, but for now, go here, grab everything, and drop it into a /src/utils directory.

The Build

This goes without saying, but you could and should have been committing this code throughout the post. At this point, save everything and navigate over to the VSTS instance. Also, one thing I would like to clarify, when I say VSTS I mean Visual Studio Online, but most if not all of these concepts can be applied to the newer versions of TFS and ReleaseManagement.  To start setting up the build, we need to create a new Build Definition in VSTS. On the Build tab of VSTS, there should be a + New, click that and you should see: New Build Definition

Select Visual Studio and click Next. Now we’ll select our repository, we are using VSTS TFS-git, but as you can see, you can use several other repositories as well: New Build Definition

Whatever you are planning on using, select it and hit Create. What you should now see is a list of Build Tasks, the default ones defined for a Visual Studio build. This will get us about half way there, so let’s add a few more things.

  • Go out to the marketplace and grab a tool to replace the tokens we put into the TDSLicence.config (like this add-on).
  • Once that’s installed, add a new Build Task to the Build Definition, find Replace Tokens in the list of tasks and hit add.
  • Update the fields as follows: root directory lib, target files as **\*.config and under Advanced, change the Token Prefix/Suffix to a double underscore.
  • It should end up looking like: Tokenization

This Replace Token Task should appear before the Build Solution task, that way our variables get passed into the license file before build, allowing for a successful build. This task can also be used to replace any config file; web.config, connectionstrings, include files, etc. Before we get any further, we also need to add/update the necessary variables; BuildConfigurationtdsOwner and tdsOwner:

Build Variables

Next add /p:OutDir=$(build.stagingDirectory) /p:UseWPP_CopyWebApplication=true /p:DeployOnBuild=true /p:SkipInvalidConfigurations=true to the MSBuild Arguments of the Build Solution **\*.sln task. This will tell MSBuild where to deploy the end site in the scope of VSTS:

Build Task


The next few tasks we will add are three Publish Artifacts (all with the Artifact Type: Server).

  • One for the Website: Path To Publish: $(build.artifactstagingdirectory)\_PublishedWebsites\Feydra.Web (update this to your project name)
  • One to publish the Utils: Path To Publish: utils
  • One for TDSPackages: Path To Publish: $(build.artifactstagingdirectory)\_Packages

It should look something like:

Build Tasks

If you want the build to be triggered on a code commit, on the Build Definition go to the Triggers tab, check the Continuous Integration (CI) checkbox, and tell it to Include: /refs/heads/master.

At this point, the initial Build Defnition should be all set!

Steve VandenBush

Technical Lead
  • Cloud
  • Sitecore
  • Visual Studio Team Services
  • VSTS
subscribe to GeekHive's newsletter

Never miss out on a Sitecore update again!

Our newsletter features a blog roundup of our top posts so you can stay up to date with industry trends, tutorials, and best practices.

The Release

To preface this entire section, the environment I am working with is an Azure Sitecore Webapp. It contains two nodes, a CM and a CD node. It was a quick and easy way to get Sitecore up and running on an environment other than my local.  Now that the build is compiling successfully and the Artifacts are being published, we can access all the artifacts from the Releases tab in VSTS. Think of a Release as the formal deploy process, in its simplest form.

The next step is to create an empty Release Definition:
New Release Definition

The second step will ask you what Project and what Build Definition you want to use for its authority (ie: what it will use to trigger a release upon successful build and what it will use as its Artifact source):
New Release Definition
If you want the release to execute on a successful build, check the Continuous deployment checkbox.

The Deploy

Now we need to add some tasks.  The first Environment we are going to define in this Release Definition is our content authoring server, or CM.  Since we are using the Azure Sitecore Web App, the first task we want to add is a Deploy Azure App Service. This should only take us associating our subscription and appliance, and it should deploy:
Deploy Task
This, in a nutshell, will Web Deploy the Website artifact over our Sitecore webroot. You are probably thinking, "well, that's great and all, but what about the transforms, what about the differences between my CD and CM instance?" That's what we'll answer next.

The Transform

Traditionally transforming happens on build, but since we are using one authoritative build, we either need to handle the differences via tokenized Variables in the VSTSBuild transformed version of the config, or include a config to do the transforming outside of the build.

Transforms in Project

Above, you see a screenshot of a config folder that organizes a few configs that will be used, with a config transform task (ConfigTransformation), to manipulate any last changes needed to distinguish environments. These configs can be configured however you need and applied surgically. I chose to take the easiest route and just make transforms specific to the instances themselves.

Transformation Task

Above is the ConfigTransform setup for my CM instance. This "variables first, surgical transforms second" approach makes you consider what changes you are trying to make from the standard Sitecore webroot.

We also want to add a Tokenization task like we did for the build in this release definition. It will be the exact same but with the Website Build artifact as the root directory (allowing tokenization on any config in the website build).

The Publish

The last part of this entire ball of wax is the actual publish of the TDS items from the initial build. This obviously will vary based on what projects are in your solution, but for our example let's pick TDS.Core, a package that holds all the TDS changes to the Core database. First, add a Powershell task:

Powershell Task

Then let's fill that task in:

  • The script path will be the path to your Utils artifact and the deploy-sitecorepackage.ps1 script
  • Arguments: <siteUrl> <packagePath> <connectionTimeout(optional)> <maxTimeout(optional)>
  • The working folder: set it to the TDS project directory to make the above args more readable
  • Fail on Standard Error if this fails, we do not want to keep installing packages

Powershell Deploy Task

You will want to repeat these steps for the other .update packages in the solution that you want to be installed.

We will also want to add is another Powershell task to actually publish these items. It will follow very similar rules to the deploy steps, but have some different values:

  • Use the publish-sitecorepackage.ps1 script in this instance from the Utils artifact
  • <siteUrl> <publishTargets> <publishMode> <connectionTimeout(optional)> <maxTimeout(optional)>
  • There's no need for Working Folder to be set in this instance

Powershell Smart Publish Task

These last handy bunch of Powershell scripts will deploy and publish your update packages to your Sitecore site, but there may or may not be one last roadblock for that work: the Sitecore.Ship Whitelist values. This varies from project to project, but since my example has build and release both using the Hosted Build/Release Agent, we don't actually know the IP address the request will come from. Below is the very last tasks needed to get this project to succeed:

  • Add a Release Variable named ReleaseIPAddr and give it the value
  • Add a Powershell Script task to the Release definition (not the same as before, this one will give us the ability to add inline script)
  • In the Task, for the Type set it to Inline Script and in the Inline Script text area, add:
  • $releaseIPAddr = Invoke-RestMethod | Select -exp ip
    Write-Host "Variable: " $(ReleaseIPAddr)
    Write-Host "Current External IP: " $releaseIPAddr
    Write-Host "##vso[task.setvariable variable=ReleaseIPAddr;]$releaseIPAddr"

    (this will set the ReleaseIPAddr variable to the IP address of the Hosted Release Agent)
  • Add <add name="vsts_release_agent" IP="__ReleaseIPAddr__" /> to the packageInstallation/Whitelist in the Web.VSTSBuild.Config

The Release Definition should look roughly like:
Release Definition

And at this point, you should be able to commit, build, and deploy all thru one single commit!

The Branch Strategy

Normally, I would follow something similar to git-flow strategy: setting up a branch or tag for each environment, leaving master mainly untouched until merging in a release and conducting all new work, unrestricted, in the origin/develop. Promotion to environments would be done thru merging the origin/develop branch into the origin/qa and so on up the chain.

In most of the recent projects we used git-flow on, we would have 1 build per environment, meaning if we had dev, qa, staging, and production environments, we would have 4 branches, with 4 builds and deploys. Ultimately this means having 4 builds that do the exact same thing and have the potential of getting out of sync (ie: someone pushes a fix to qa or beta, never merges back into develop and some config files get left hanging out on the server). Given the confines of VSTS, we figured it's time for a different approach.

Introducing our plan: its simple, 1 build to rule them all!  Not entirely true, but our original goal was to have one branch trigger all builds and be the authority for all builds that get promoted. What we wanted was something strict, clean, and easy to follow.  The strategy we landed on that seems to have been working well so far is:

  • All builds built off of the origin/master (triggered)
  • All feature development is built through Pull Requests and named in a feature/branch scheme (name it logically, by user story, number, or whatever you need to distinguish them)
  • All merges are done thru approving Pull Requests (developers are not able to commit/merge directly to origin/master)
  • Develop has a triggered release to update the develop environment automatically
  • All upper environments are done manually upon approval of development
  • When the need for a hotfix arises:
    • Feature level Pull Request approvals freeze (development can continue, but no dev builds will be triggered)
    • A hotfix/branch is created off of origin/master at the commit/tag that production is at
    • A manual build is created off of the hotfix/branch, and promoted thru the dev/qa/staging servers to eventually make it to production
    • Once that is completed, the hotfix is approved/merged back into origin/master and feature level PRs are unfrozen

There are most likely caveats to this approach that we have not run into yet, but it has proven to be fairly straightforward and clean thus far.

Hopefully, this outlined process can help you define your own artifact promotion build process on VSTS and you will never have to touch a NAnt or CCNet config ever again!

If you found this post useful, check out my other posts here


Recent Work

Check out what else we've been working on