This is your brain. This is your brain on computers.

Introduction to Azure Pipelines for Continuous Integration

What Is Azure Pipelines?

Straight from the creators, Microsoft:

Get cloud-hosted pipelines for Linux, macOS, and Windows. Build web, desktop and mobile applications. Deploy to any cloud or on‑premises.
Automate your builds and deployments with Pipelines so you spend less time with the nuts and bolts and more time being creative.

Azure Pipelines Page

Azure Pipelines is Microsoft’s approach to Continuous Integration and Continuous Deployment (CI/CD).

In the most basic terms, you can create step-by-step processes (pipelines) that will pull down your code, build it, test it, and deploy it. It’s all hosted in Azure on virtual machines that you can select. You can customize the pipelines to fit whatever scenarios you have for you, your organization, and your products. Don’t need to deploy anything? No problem, just don’t add any deployment steps. Continuous building/testing is a step above most.

Tasks in your pipelines can be as high level as drag-and-drop or as low level as writing your own customer command line scripts. This makes it easy to create quick proofs of concept when needing to see if your scenarios will work while at the same time supporting deep-driven customized tasks.

Pricing options can be a bit strange and hard to understand simply because of how many variables there are in the process of selecting what you want. As of this writing (Mar 31, 2020), this is what we’ve come to understand:

  • Free for Individual Users: 1 Microsoft-hosted job with 1800 minutes and 1 self-hosted job with unlimited minutes
  • Free for 5 Users: Everything from the above bullet + Azure Boards, Repos, and Artifacts
  • Free for GitHub Users: 10 Microsoft-hosted jobs with unlimited minutes for public repositories and 1 Microsoft-hosted job with 1800 minutes for private repositories

In this guide, we’ll be going through the “Free for GitHub Users” flow.

Why Use Azure Pipelines?

If you’ve never been exposed to a well-maintained CI/CD process, you may be asking yourself, “Why do I need to use something like this?”

When you’re by yourself, working on a product of your own making without any input from other developers, the necessity for automated builds and releases is sincerely reduced. But those types of projects are usually just proofs of concept, learning projects, or minor in nature. Any large, useful, valuable product should accept input from a diversity of users and developers with an established automated build and release process to reduce errors in transmission.

Errors in transmission? Huh?

Anytime we rely on humans to do something, we are risking a large amount of failure in the task being completed simply due to the fact that humans are prone to errors. We’re imperfect creatures. Computers were designed in part to make up for our failings and ability gaps.

Using tools like Azure Pipelines, we can take our manual build and release processes, create the automated versions of those processes, and remove all manual access except to only the most trusted administrators. This effectively removes humans from the equation which is a huge benefit for efficiency and accuracy.

Get Accounts Set Up

Before you get started, make sure you have a Microsoft account (for Azure Pipelines) and a GitHub account (for code repos).

Start by signing up to Azure Pipelines using the GitHub sign up flow.

If you have a GitHub account, you’ll be asked to log in using your GitHub credentials. If you don’t have a GitHub account, get one!

Install the Azure Pipelines app from the GitHub marketplace to link your accounts.

NOTE: You may need to provide your GitHub password and your Microsoft account password.

Finish the process by going through the prompts to set up your Azure Pipelines account, organization, and projects. Wait for the process to finish bootstrapping your Azure Pipelines account.

Explore Azure Pipelines UI

In your Azure Pipelines account UI, located at a URL in the format of:<organization>/<project> you should see your organization and project set up as you completed in the previous step.

Go to the Pipelines page, located at a URL in the format of:<organization>/<project>/_build. Here is where you’ll create, delete, manage, and view results of the pipelines you’ve created. You can further manage deployment environments, variable libraries, secret files, task groups, and deployment groups.

If you signed up for the “Free for 5 Users” plan, you should also see Azure Boards, Repos, and Artifacts sections.

Create New Pipeline

In the upper right, click New pipeline to create a new pipeline.

Click the GitHub – YAML option to create a pipeline based on your GitHub account. YAML is the file type that you’ll be editing to create the pipeline. You can obviously select other options based on your scenarios, but this guide assumes you’ve linked to your GitHub account for the purposes of creating pipelines based on those repos.

Click a repository to use as the base for the pipeline’s build process. You’ll notice that repositories will be flagged as private, forked, or public (indicated by no flag). Remember that private repos will have reduced job numbers based on the free pricing tier.

Select a pipeline template from the list that matches whatever project you’re trying to build and deploy. Microsoft prioritizes their product template types, so you may need to click the Show more button if you’re using technologies like Python, Java, or Go. If you don’t want to use a template, use the Starter pipeline option for a blank YAML file.

The following sections assume you clicked the Starter pipeline option.

Basic Pipeline

The blank starter template looks something like this:

# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:

- master

  vmImage: 'windows-latest'

- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Add other tasks to build, test, and deploy your project.
    echo See
  displayName: 'Run a multi-line script'

There’s a few things to note here:

  • Trigger: This determines how your pipeline is started. In this case, any time a commit is made against the master branch, the pipeline will start.
  • Pool: This determines which agent from the agent pool to perform the build steps. In this case, it uses a virtual machine image for Windows.
  • Steps: This lists out the steps of your build process to successfully build your project. In this case, it does nothing useful but does demonstrate basic syntax.

Add Build and Test Tasks

First, add basic variables like so. The solution variable to determine which solution to build as part of your project. The buildPlatform and buildConfiguration variables will be used to determine the target platform and project configuration to build respectively. These are the msbuild options to be used during the build steps.

  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

Now add NuGet installer and NuGet restore tasks. The first will install the latest version of NuGet on the virtual machine performing the build. The second will run the nuget restore command against the solution identified in your solution variable.

- task: NuGetToolInstaller@1

- task: NuGetCommand@2
  displayName: NuGet Restore
    restoreSolution: '$(solution)'

Next add a Visual Studio Build (VSBuild) task. Note that this specific task will be dependent on your project type. VSBuild, MSBuild, .NET Core, Maven, Ant, CMake, and command line are just a few of the options. In this example, we use VSBuild. Also note that you are referencing the variables defined above.

- task: VSBuild@1
  displayName: Build (Visual Studio)
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

Finally, add a Visual Studio Test (VSTest) task. Like VSBuild, there are other options to perform testing based on your use case and project type.

- task: VSTest@2
  displayName: Test (Visual Studio)
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

If that’s all you need in for your use case, then you can stop here and go down to the Testing the Pipeline section. At this point, your pipeline is set up to trigger on any commit to the master branch, pull down the latest master branch, install NuGet, restore NuGet dependencies on your project, build the project, and test the project.

Deploy NuGet Package

If you’re building a library using .NET, you might want to think about publishing your library to to share your library with the world. Keep in mind that this might not be a good idea if you have a private repository for your business. In that case, you can look into using Azure Artifacts to create a private NuGet feed.

Like VSBuild and VSTest caveats described previously, there are multiple ways to accomplish the process of pushing NuGet packages. Microsoft does a good job at providing many options with backwards compatibility, but that sometimes comes at the price of confusion about which approach is recommended. See below for both ways.

Using NuGet Tasks

The first and older way to managing NuGet packages is to use the NuGet tasks directly.

First create a nuget pack task that will create the nupkg file for publication. Update the packagesToPack property to identify which csproj files you want to package in this process.

- task: NuGetCommand@2
    command: 'pack'
    packagesToPack: '**/*.csproj'
    versioningScheme: 'off'

Before you can push to an external NuGet feed, you need to create a Service connection in the Azure DevOps UI.

In the lower left, click the Project settings link. Then click the Service connections link to go to the Service connections UI, located at a URL in the format of:<organization>/<project>/_settings/adminservices.

In the upper right, click New service connection to begin creating a new one.

Select NuGet from the list of providers. Note that this is where you would make external connections to other popular tools like Jira, Jenkens, and npm.

If you’re deploying to, you’ll need to use the ApiKey authentication method. You can receive this key at your profile page by generating a new API Key for specific package feeds. The Feed URL should be:

The Service connection name can be whatever you want, but you need to remember it because you’ll directly reference that name in the nuget push task that will be created in your pipeline YAML.

Click Save to create your Service connection.

Now you can add the nuget push task to reference the Service connection name in the publishFeedCredentials setting of the task (see below). The name has to match exactly (including spaces).

- task: NuGetCommand@2
    command: 'push'
    packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg'
    nuGetFeedType: 'external'
    publishFeedCredentials: ''

Using .NET Core Tasks

The newer way to manage your NuGet packages is through the .NET Core CLI tasks. Note that these tasks may not apply to your scenario if you’re building older .NET Framework projects. In that case, use the NuGet tasks described above.

Start by adding a dotnet pack task as seen below. Update the packagesToPack property to whatever csproj file you’re packing.

- task: DotNetCoreCLI@2
  displayName: NuGet Pack (if tagged)
    command: 'pack'
    packagesToPack: 'SharpDL/SharpDL.csproj'
    versioningScheme: 'off'

Remember all the work above we completed regarding setting up a Service connection to push to an external feed? Well, bad news. We can’t use it in the .NET Core CLI push task. More specifically, the task does support referencing a Service connection, but the .NET Core CLI doesn’t support pushing to a feed using an encrypted API key (which is how the Service connection reference works).

There are two workarounds to that issue. You can use the nuget push task described above or follow the custom dotnet nuget push described below. Pay attention to the comments above the task to understand why we used the -n flag and the --skip-duplicate flag when pushing.

# Deploy release to
# Custom nuget command has to be used to workaround issue where dotnet core doesn't support encrypted API Key publication
# -n to skip symbols
# --skip-duplicate to prevent publishing the same version more than once
- task: DotNetCoreCLI@2
  displayName: Push NuGet Package (if tagged)
    command: custom
    custom: nuget
    arguments: >
      push $(Build.ArtifactStagingDirectory)/**.nupkg
      -s $(NuGetSourceServerUrl)
      -k $(NuGetSourceServerApiKey)
      -n true

But what about the NuGetSourceServerUrl and NuGetSourceServerApiKey variables? Where are those defined? In the upper right of your YAML editor, click the Variables button.

Click New variable.

Create the following variables to be referenced in the YAML pipeline configuration. These values should be the same that you set up earlier in the Service connection section.

  • NuGetSourceServerUrl =
  • NuGetSourceServerApiKey = <key from profile> (mark this one as secret)

Deploy GitHub Release

In addition to publishing to, you can publish your build artifacts to GitHub using Releases tied to your repo. This option makes more sense if you are building an executable or standalone product that doesn’t benefit from a package manager like NuGet.

Since GitHub is considered an external source like in the previous section, you’ll need to set up a Service connection to GitHub.

Start by creating a new Service connection exactly as you did in the NuGet section above. Instead of selecting NuGet from the list of connections, select GitHub.

From the options in the next window, select Grant authorization (OAuth), select the only choice in the OAuth Configuration drop down, and name your Service connection whatever you want. But make sure you remember it so you can reference it exactly in the YAML configuration.

Click the Authorize button to go through the OAuth authentication flow. This will grant permissions on GitHub to allow Azure Pipelines to push releases to your repositories.

Click Save to create the Service connection.

Use the GitHub Release task as seen below.

- task: GitHubRelease@1
  displayName: Push GitHub Release (if tagged)
    gitHubConnection: '<service connection name>'
    repositoryName: '<repo name>'
    action: 'create'
    target: '$(Build.SourceVersion)'
    tagSource: 'gitTag'
    changeLogCompareToRelease: 'lastFullRelease'
    changeLogType: 'commitBased'

Here’s a breakdown of the properties:

  • gitHubConnection is the name of your new Service connection.
  • repositoryName is the name of whatever repository you’re building.
  • tagSource as gitTag will take the tag related to the commit for downstream use (for example, the name of the release)
  • changeLogCompareToRelease as lastFullRelease will compare the new release to the previous full release to generate a changelog
  • changeLogType as commitBased will generate a changelog consisting of commit details for each line

Using Task Settings Helpers

We agree that much of the task properties are esoteric and require frequent trips to the task’s documentation to understand all the options. Fortunately, every task has a built-in task generation helper in the YAML editor. Above the task in the YAML editor, click the Settings button.

On the right of the editor, you’ll see the settings helper UI popup. In this example, we are looking at the GitHub Release task settings. Use this UI to experiment with all the options available to you. When you click the Add button to add the task, it will paste the YAML into the editor. Through trial and error, you should be able to deduce what the translation is between the helper UI and the eventual YAML output. Combined with the official documentation, you’ll be an expert in no time!

Testing the Pipeline

After all that work, how do we know anything is going to work? Try it out! There are two main ways in which you can test this pipeline: manually or through the intended trigger mechanisms in the YAML configuration.

Test Manual Trigger

First, you can manually trigger the pipeline to run. Create your pipelines as described above and use the pipelines UI to select a specific pipeline. Once your pipeline is selected, click the Run pipeline button.

To the right, you’ll see the Run pipeline window popup. There are a lot of options to configure the specific run, but for this example, we just need to select which branch to build.

Click Run to begin the build process.

Test Configured Trigger

Second, you can perform an action based on whatever your pipeline is set to trigger. In this guide, we created a pipeline that would trigger based on commits to a master branch. Using your favorite Git tool, go make a commit to master and push to your GitHub remote.

In the GitHub’s commit UI for your repository, you’ll see a new icon next to your commit which indicates the build process status. Yellow = pending. Red = build failed. Green = build passed. You can use these as quick indicators if action needs to be taken to correct possibly broken builds.

View Results

Regardless of which method you used to trigger the pipeline, you can view the results in the same location. At the main pipelines UI, click the specific pipeline that you want to view.

You’ll see a list of runs that have been started or completed based on whatever pipeline you chose. Check for the branch, commit, status, and duration of the run to learn more about whatever the status indicates.

Click the specific run to view details about the run including metadata, task errors/warnings/messages, and information about the specific jobs that were performed as part of the run. A job is a collection of tasks that run in a sequential order. These are the tasks you set up in the previous steps of this guide.

Click the Job to view the details about each task.

In the detailed list of tasks for the job you selected, you will see the status of the task (pending, failed, skipped, completed) and cycle through each task to see its detailed output. You’ll also see the duration of each task so you can easily identify problem tasks that are contributing to long-running jobs.

For example, as seen below, clicking the VSBuild task that we set up earlier, you can see the detailed console output from the build process. This helps you understand why your task failed.

Justin Skiles

Justin Skiles

Justin has been developing enterprise application software for over 10 years primarily using Microsoft stacks, Azure, and various open source tools. He has most recently been trying his best as a Manager and Director of Software Engineering in the health care industry.

Share the Knowledge

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on facebook
Share on twitter
Share on linkedin
Share on pinterest

Follow our updates



Keep Exploring. Choose Your Path.

Be a Better, Smarter You