In the first two parts of this series, I shared my secret recipes with you and showed how I structure my YAML pipelines in Azure DevOps. In the first part, we went over the techniques to efficiently reuse parts of a build pipeline with templates. In the second part, I showed how to push that template further using conditions only to execute portions of the template, making it highly reusable for different contexts.

A bit of history

Azure DevOps used to have two distinct sections for CI (Builds) and CD (Releases). They were mostly based on click configuration, which made them hard to maintain and automate. It was also pretty hard to follow the flow of building and deploying an application as it was split into two different sections.

Those two sections are still there and now referred to as the "Classic" pipelines. They are getting slowly merged and unified into the new YAML pipelines.


YAML pipelines bring a lot of advantages over the classic way of doing things, namely;

  • Pipeline configuration lives in git, which brings all the upsides of source control (audit trail, history of the file, etc.)
  • Easy to move and replace parts with a simple copy/paste
  • Easy to export
  • Combines CI and CD in the same pipeline (the main point of this blog post)


Now that we have an excellent build pipeline, we'll talk more about the stages in Azure DevOps and how they're useful. Stages are the phases of a Continous Integration and Continous Delivery (CI/CD) pipeline. So far, we've only seen the CI part, where the pipeline kicks-in when a new commit is pushed to git. What we want to achieve in order is:

  1. Build the code
  2. Run tests
  3. Run any other needed validation
  4. Package
  5. Deploy the package to environment 1
  6. Deploy the package to environment 2
  7. etc

Steps 1 through 4 are what we did in the previous two posts; we'll now work on step 5 and 6.

My Recipe

The way I structure my stages is always the same. One stage to Build and package and one stage per environment where I want to deploy the application. Building that nice chain of stages will give you what we call package promotion. The idea is to build the package once and promote the same bits from one environment to another. It brings two main advantages:

  • It's faster to build and roll out as you only build it once
  • It gives you the confidence to deploy to the next environment as you are pushing the EXACT same bits. No surprises.

Enough talking show me the code

First of all, we'll create a new template for the deployments.


- name: envCode
  type: string
- name: webAppName
  type: string

- deployment: 'deploy'
  displayName: '${{ parameters.envCode }} deployment'
  environment: '${{ parameters.envCode }}'
        - task: AzureRmWebAppDeployment@4
              ConnectionType: 'Your connection name here'
              azureSubscription: 'Your subscrpition here'
              appType: 'webApp'
              WebAppName: '${{ parameters.webAppName }}'
              packageForLinux: '../**/*.zip'

It's a simple deployment template that will deploy to an Azure AppService nothing fancy here.

You must first configure a connection to Azure in the Azure DevOps organization settings and reference it by name.

Now let's complete our pipeline.

  batch: true
    - Sources/PathToSln/*

- stage: BuildAndPackage
  displayName: 'Build and package'
  - template: 'templates/'
      solutionPath: Sources/PathToSln/
      clientAppPath: Sources/PathToSln/WebApp/ClientApp/
- stage: Dev
  displayName: 'Dev deployment'
  - template: 'templates/'
      envCode: 'Dev'
      webAppName: 'the name of your dev app service here'
- stage: Prod
  displayName: 'Prod deployment'
  condition: eq(variables['build.sourceBranch'], 'refs/heads/master')
  - template: 'templates/'
      envCode: 'Prod'
      webAppName: 'the name of your prod app service here'

Let's unpack this

Notice the subtle condition on the Prod stage? That condition will make sure to only promote to production builds that come from the master branch in git. It means that any other branch will still trigger the pipeline and push to the Dev environment but will skip the Prod' stage. It will give us an automated Continuous Delivery to the Dev` environment without any manual intervention.

There are other ways to block a stage from executing. E.g., waiting for manual approval, but it will be for an upcoming post.


In the series, we've built a reusable template and a complete CI/CD pipeline in a few lines of code. At this point, building the next pipeline for another application or microservice will simply be copy/pasting less than 30 lines of YAML and changing a few variables to make it up and running. How productive is that? I hope you enjoy the series, and don't be shy to ask any questions in the comments 👇.