The Situation

The development teams run on a two-week release cycle. The final three days of the cycle they are in code freeze. Code is deployed to two environments: Test and Production.

During code freeze:

  1. At the start, all repos are built and released to Test. This is the production code.
  2. Newly developed code must be committed to the repository and built, but it must not be released to Test.
  3. At the end, all production code is deployed to Production, and newly developed code is deployed to Test.

Finally, the teams want to have just one build and one deployement definition for each solution.



Git is configured with two long-lived branches: master and development.

  • Developers always work against the development branch, pushing their local, short-lived feature branches.
  • At code freeze, development is merged into master.
  • If there are emergency production deployments, ideally they're made first to deployment and then to master. If they're made directly to master then the changes need to be merged into development (or, ideally, development is rebased onto master)

TFS Build

The build definition is configured with Triggers, specifically Branch filters for both master and development branches. Continuous Integration is enabled.

This allows commits to both development and master branches to trigger the build.

TFS Release

The deployment definition is configured in the following way:


There's a single artifact with a Source type of Build. The Source is the build definition.

Continuous deployment is enabled, and both branches are allowed.

Both environments use the After release trigger.

Test Environment
The artifact filters include both branches

In the sample, a Copy Files task is used to move the binaries to their release folder. While not required for the solution, it demonstrates using variables to create release subfolders. In this case, folders for branch and build number are created. Also note the Overwrite and Flatten Folders checkboxes.

Production Environment
The differences between this and the Test environment are

  • Only the master branch is included in the artifact filters
  • Approvers are added
  • Only the latest build is deployed upon approval

Code Freeze

What has to happen during the code freeze?

Development branch commits don't deploy. That's it.

There are at least two ways to prevent development branch deployments during code freeze.

  1. Add a build tag using a custom task that checks for the presense of a particular file. A custom build task would be used for this.

    Sorry, I don't have an example of this. But I've seen it done in a real-world environment.

  2. Use a Gated release. For example, check a web service endpoint. If it fails to return, you're in code freeze and the gate fails.

Recommendation: Stick with build tags

The disadvantage to tagging builds is that the custom task needs to be added to each build definition. The advantage is that the tag is part of the artifact trigger.

The disadvantage to gates is that there's a required timeout period of at-minimum 6 minutes before the gate fails. The advantage is that build definitions don't need to create tags.

REST API Gate Sample

Web API Endpoint Sample

   public class InCodeFreezeController : ApiController
        public IHttpActionResult Get()
            //IsInCodeFreeze checks some condition. Could be the same file-existence
            //check that's used for tagging today.
            //Not ideal, but simpler for TFS Gate to just fail the request
            if (IsInCodeFreeze)
                return BadRequest();
            return Ok();