CI/CD Pipeline with Azure DevOps

Build an automated CI/CD pipeline that builds and tests a .NET Web API on every push.

Introduction

30 Second Summary

Imagine pushing code to your team's repository and finding out hours later that it broke the entire application. In professional software teams, automated pipelines catch these problems the instant code is pushed.

In this project, you will build an automated CI/CD pipeline in Azure DevOps that builds and tests a .NET Web API every time you push code to GitHub.

What You'll Build

A fully automated CI/CD pipeline that builds and tests an ASP.NET Core Web API every time you push code to GitHub, running on a self-hosted agent on your own machine.

By the end of this project, you'll have:

  • A .NET 10 ASP.NET Core Web API with a testable Calculator service and unit tests.
  • A self-hosted Azure Pipelines agent running on your Windows machine, connected to your Azure DevOps organization.
  • A YAML pipeline that automatically restores dependencies, builds your project, and runs tests on every push.
  • Secret Mission: Add code coverage reporting to your pipeline and display a build status badge on your GitHub README.

Are there any prerequisites?

You'll need the .NET 10 SDK, Git, and VS Code installed on a Windows machine. You'll also need a GitHub account. Step 1 walks you through setting up your free Azure DevOps account.

Before We Start

Before We Start...

Set Up Azure DevOps

Every professional development team uses a CI/CD pipeline to catch bugs before they reach production. The pipeline automatically builds and tests your code every time you push a change.

To build our pipeline, we need a home for it. Azure DevOps is Microsoft's platform for managing code, pipelines, and project work. In this step, you'll create your Azure DevOps account and set up a project to house the pipeline.

In this step, get ready to:
  • Sign up for Azure DevOps with a personal Microsoft account.
  • Create a new project called WeatherApi.
Sign up for Azure DevOps

Azure DevOps is free for individual use. You get one self-hosted CI/CD concurrent job included at no cost, which is exactly what this project uses.

  • Open your browser and go to dev.azure.com.
  • Select Get started with Azure.
  • Sign in with your personal Microsoft account. If you don't have one, select the option to create a new Microsoft account.
  • Select Try Azure for free and complete the sign-up process.
  • In the Azure portal, select the app launcher (grid icon in the top-left corner), then select DevOps.
  • Select Create new organization.
  • Enter a name for your organization, choose your hosting geography, and select Continue.

After sign-up, your organization is accessible at https://dev.azure.com/{Your_Organization}.

What is an Azure DevOps organization?

An organization is the top-level container that groups your projects together. Think of it like your company's workspace. Inside it, you create projects for each application or team.

Stuck on the sign-up flow?

Make sure you're using a personal Microsoft account (like an Outlook or Hotmail address), not a work or school account. Work accounts may require admin approval.

If you don't see the DevOps option in the app launcher, try navigating directly to dev.azure.com after completing the Azure sign-up.

help me sign up for Azure DevOps

Create a new project

A project in Azure DevOps is where your repository, pipelines, and work items live. You'll create one project for the Web API you'll build in this course.

  • From your organization's home page, select New project.
  • Enter WeatherApi as the project name.
  • Set Visibility to Private.
  • Expand Advanced to see the version control options.
  • Confirm that Version control is set to Git.
  • Select Create.

Azure DevOps displays the project welcome page. You should see your project name WeatherApi in the top-left corner of the page.

Why Private visibility?

Private projects restrict access to only the people you invite. This keeps your pipeline and code visible only to you while you're learning.

Don't see the New project button?

You need to be the organization owner or a member of the Project Collection Administrators group. Since you just created the organization, you should automatically have this permission.

If you still don't see it, try refreshing the page or signing out and back in at dev.azure.com.

help me create a project in Azure DevOps

Your Azure DevOps organization and project are ready. Next up, you'll create the .NET Web API project on your local machine and give it something testable for the pipeline to build.

Create the .NET Project Locally

Your Azure DevOps project is ready and waiting for code. Before you can automate builds and tests, you need something to build and test.

In this step, you'll scaffold an ASP.NET Core Web API, add a Calculator service with testable methods, and write unit tests using xUnit. By the end, you'll have a project that passes all tests locally.

In this step, get ready to:
  • Scaffold a .NET Web API project with a Calculator service.
  • Create an xUnit test project with unit tests for the Calculator.
  • Verify all tests pass locally before automating.
Scaffold the Web API and add a Calculator service

The .NET CLI can scaffold a full Web API project in one command. You'll then add a simple Calculator class that gives your pipeline something meaningful to test.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Open a terminal inside VS Code by pressing Ctrl+` (macOS and Windows).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir WeatherApi
cd WeatherApi
  • Scaffold the Web API project by running:
dotnet new webapi -n WeatherApi

What does this command do?

The dotnet new webapi template generates a complete ASP.NET Core Web API project with controllers, configuration, and a .csproj file. The -n WeatherApi flag names the project and places it inside a WeatherApi subfolder.

  • Confirm the project was created by running:
ls WeatherApi

You should see files including WeatherApi.csproj and Program.cs in the output.

  • Open the WeatherApi folder (the one on your Desktop) in VS Code by clicking File in the top menu bar.
  • Click Open Folder.
  • Navigate to Desktop and select the WeatherApi folder, then click Open.

Why open the folder?

Opening the top-level WeatherApi folder as your workspace means both your Web API project and your test project (created next) will be visible in the Explorer sidebar. The integrated terminal will also start in this folder automatically.

Now you'll add the Calculator service that your pipeline will test.

  • In the VS Code Explorer sidebar, expand the WeatherApi subfolder.
  • Right-click the WeatherApi subfolder and select New File.
  • Name the file Calculator.cs and press Enter.
  • Paste the following code into Calculator.cs:
namespace WeatherApi;

// A simple calculator service with methods the pipeline can test
public class Calculator
{
    // Returns the sum of two integers
    public int Add(int a, int b)
    {
        return a + b;
    }

    // Returns the difference between two integers
    public int Subtract(int a, int b)
    {
        return a - b;
    }

    // Returns the product of two integers
    public int Multiply(int a, int b)
    {
        return a * b;
    }
}

What does this code do?

This is a plain C# class with three arithmetic methods. It has no dependencies on ASP.NET Core, which makes it easy to test in isolation.

The pipeline will use these methods to prove that your automated build and test process works. If someone accidentally breaks the math, the pipeline catches it.

  • Save Calculator.cs by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Don't see the WeatherApi subfolder in Explorer?

Make sure you opened the top-level WeatherApi folder from your Desktop, not the nested project subfolder. The Explorer should show a WeatherApi folder containing WeatherApi.csproj.

Help me figure out which folder to open in VS Code

✔️ Awesome, I've got everything!

Great. Make sure you have saved Calculator.cs before moving on.

ⓧ I'd like to double check the full code

Here is the complete Calculator.cs file:

namespace WeatherApi;

// A simple calculator service with methods the pipeline can test
public class Calculator
{
    // Returns the sum of two integers
    public int Add(int a, int b)
    {
        return a + b;
    }

    // Returns the difference between two integers
    public int Subtract(int a, int b)
    {
        return a - b;
    }

    // Returns the product of two integers
    public int Multiply(int a, int b)
    {
        return a * b;
    }
}
Create the test project

Unit tests verify that your Calculator methods return the correct results. You'll create a separate test project using xUnit, then wire it up to reference your Web API project.

  • Open the terminal in VS Code by pressing Ctrl+`.
  • Create the xUnit test project by running:
dotnet new xunit -n WeatherApi.Tests

What does this command do?

The dotnet new xunit template generates a test project pre-configured with the xUnit testing framework. The -n WeatherApi.Tests flag names it and creates a WeatherApi.Tests subfolder alongside your Web API project.

  • Add a reference from the test project to your Web API project by running:
dotnet add WeatherApi.Tests/WeatherApi.Tests.csproj reference WeatherApi/WeatherApi.csproj

Why add a project reference?

The test project needs to access the Calculator class from your Web API project. The dotnet add reference command links the two projects so the test code can import and call your Calculator methods.

The xUnit template generates a default test file called UnitTest1.cs. You'll replace it with your own test file.

  • Delete the default test file by running:
rm WeatherApi.Tests/UnitTest1.cs
  • In the VS Code Explorer sidebar, expand the WeatherApi.Tests folder.
  • Right-click the WeatherApi.Tests folder and select New File.
  • Name the file CalculatorTests.cs and press Enter.
  • Paste the following code into CalculatorTests.cs:
using WeatherApi;

namespace WeatherApi.Tests;

public class CalculatorTests
{
    // Create a single Calculator instance for all tests
    private readonly Calculator _calculator = new();

    [Fact]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Verify that 3 + 5 equals 8
        var result = _calculator.Add(3, 5);
        Assert.Equal(8, result);
    }

    [Fact]
    public void Subtract_TwoNumbers_ReturnsDifference()
    {
        // Verify that 10 - 4 equals 6
        var result = _calculator.Subtract(10, 4);
        Assert.Equal(6, result);
    }

    [Fact]
    public void Multiply_TwoNumbers_ReturnsProduct()
    {
        // Verify that 3 * 7 equals 21
        var result = _calculator.Multiply(3, 7);
        Assert.Equal(21, result);
    }
}

What does this code do?

Each method marked with [Fact] is a test that xUnit will discover and run automatically. The naming pattern Method_Scenario_ExpectedResult makes test output easy to read.

The Assert.Equal call checks that the actual return value matches the expected value. If any assertion fails, the test fails and the pipeline will report it.

  • Save CalculatorTests.cs by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Seeing a red squiggly under Calculator?

Make sure the using WeatherApi; line is at the top of your file. Also confirm that you ran the dotnet add reference command earlier. Without the project reference, the test project cannot find the Calculator class.

Help me troubleshoot the project reference

✔️ Awesome, I've got everything!

Great. Make sure you have saved CalculatorTests.cs before moving on.

ⓧ I'd like to double check the full code

Here is the complete CalculatorTests.cs file:

using WeatherApi;

namespace WeatherApi.Tests;

public class CalculatorTests
{
    // Create a single Calculator instance for all tests
    private readonly Calculator _calculator = new();

    [Fact]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Verify that 3 + 5 equals 8
        var result = _calculator.Add(3, 5);
        Assert.Equal(8, result);
    }

    [Fact]
    public void Subtract_TwoNumbers_ReturnsDifference()
    {
        // Verify that 10 - 4 equals 6
        var result = _calculator.Subtract(10, 4);
        Assert.Equal(6, result);
    }

    [Fact]
    public void Multiply_TwoNumbers_ReturnsProduct()
    {
        // Verify that 3 * 7 equals 21
        var result = _calculator.Multiply(3, 7);
        Assert.Equal(21, result);
    }
}
Run your tests locally

Before automating anything, you want to confirm your code compiles and all tests pass on your own machine.

  • Run the test suite by running this command in your terminal:
dotnet test

You should see output showing that all 3 tests passed with no failures.

What does dotnet test do?

The dotnet test command restores dependencies, builds both projects, and runs every method marked with [Fact] in your test project. It reports which tests passed and which failed.

This is the same command your pipeline will run automatically on every push. If it passes here, it will pass in your pipeline too.

Seeing test failures or build errors?

Check that the namespace in Calculator.cs is namespace WeatherApi; and that CalculatorTests.cs has using WeatherApi; at the top.

Also confirm you ran dotnet test from the top-level WeatherApi folder on your Desktop, not from inside a subfolder.

Help me debug why my Calculator tests aren't passing

Your code compiles cleanly and all tests pass. Next up, you'll push this project to GitHub and connect it to Azure DevOps so your pipeline has a repository to watch.

Push to GitHub and Connect to Azure DevOps

Your .NET project builds and tests pass locally. The next step is getting that code into a GitHub repository where Azure Pipelines can access it.

For Azure DevOps to automatically build and test your code, it needs a connection to the repository hosting your source. You will push your code to GitHub, then create a service connection so Azure Pipelines is authorized to pull from that repo.

In this step, get ready to:
  • Push your local project to a GitHub repository.
  • Create a GitHub service connection in Azure DevOps.
Create a GitHub repository and push

Your code lives only on your machine right now. A GitHub repository gives Azure Pipelines a place to watch for new commits and automatically start builds.

  • Open your browser and go to github.com.
  • Sign in to your GitHub account.
  • In the upper-right corner of any page, click the + icon, then click New repository.
  • In the Repository name field, type WeatherApi.
  • Select Public for repository visibility.
  • Leave all checkboxes unchecked (do not add a README, .gitignore, or license).
  • Click Create repository.

Why leave the repo empty?

You already have files locally. If GitHub initializes the repo with a README or .gitignore, your first push will hit a merge conflict. Starting empty lets you push your existing code cleanly.

GitHub now shows a Quick Setup page with a URL for your new repository. Keep this page open.

Now you will initialize Git in your local project and push everything up.

  • Switch back to VS Code and open the integrated terminal by pressing Ctrl+` (Windows) or Cmd+` (macOS).
  • Make sure your terminal is in the root folder that contains both WeatherApi/ and WeatherApi.Tests/.
  • Generate a .NET-appropriate .gitignore and initialize your Git repository by running these commands:
dotnet new gitignore
git init
git add .
git commit -m "Initial commit"

What do these commands do?

  • dotnet new gitignore creates a .gitignore file that excludes build outputs like bin/ and obj/ from version control.
  • git init turns your project folder into a Git repository.
  • git add . stages all your project files for the first commit.
  • git commit -m "Initial commit" saves a snapshot of your project in Git history.
  • You should see output listing the files committed. Confirm the commit succeeded by checking for a message like create mode 100644 WeatherApi/Calculator.cs in the terminal output.
  • Connect your local repository to GitHub and push your code by running these commands:
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/WeatherApi.git
git branch -M main
git push -u origin main

What do these commands do?

  • git remote add origin links your local repository to the GitHub repository you just created.
  • git branch -M main renames your default branch to main so it matches GitHub's default branch name.
  • git push -u origin main uploads all your committed code to GitHub.
  • Refresh the WeatherApi repository page on GitHub. You should see your project files listed, including WeatherApi/Calculator.cs and WeatherApi.Tests/CalculatorTests.cs.

Push failed or asked for credentials?

  • If Git prompts you for a username and password, GitHub no longer accepts passwords for HTTPS pushes. Follow the browser sign-in prompt that appears, or set up a personal access token.
  • If you see remote: Repository not found, double-check the URL matches your GitHub username and the repo is named WeatherApi.

Ask for help: Why is my git push to GitHub failing?

Connect GitHub to Azure DevOps

Azure Pipelines needs permission to read your GitHub repository. A service connection is the bridge that authorizes Azure DevOps to access external services like GitHub.

  • Return to your Azure DevOps project at dev.azure.com.
  • In the bottom-left corner, click Project settings.
  • In the left sidebar under Pipelines, click Service connections.
  • Click New service connection.
  • Select GitHub from the list, then click Next.
  • Under Choose authorization, select Grant authorization.
  • Click Authorize. A GitHub sign-in page opens in a new window.
  • Sign in to GitHub if prompted, then approve the permissions request.
  • Back in Azure DevOps, enter a Service connection name. Type GitHub to keep it simple.
  • Check the box for Grant access permission to all pipelines.
  • Click Save.

What is a service connection?

A service connection stores the credentials Azure Pipelines needs to talk to an external service. By using OAuth (Grant authorization), you avoid managing tokens manually. Azure DevOps securely stores the authorization so your pipelines can pull code from GitHub.

  • You should see your new GitHub service connection listed on the Service connections page.

Authorization popup didn't appear?

  • Check that your browser is not blocking popups for dev.azure.com. Allow popups and try clicking Authorize again.
  • If you are signed into multiple GitHub accounts, make sure the popup signs in with the account that owns the WeatherApi repository.

Ask for help: The GitHub authorization popup isn't working in Azure DevOps. How do I fix it?

Your code is now on GitHub and Azure DevOps has permission to access it. Next up, you will set up a self-hosted agent so Azure Pipelines has a machine to run builds on.

Set Up the Self-Hosted Agent

Your code is on GitHub and Azure DevOps knows where to find it. Now you need something to actually run your pipeline.

Azure Pipelines needs an agent to execute your build and test steps. An agent is a piece of software that listens for pipeline jobs and runs them on the machine where it's installed. You'll set up a self-hosted agent on your own Windows machine.

In this step, get ready to:
  • Create a Personal Access Token (PAT) to authenticate your agent.
  • Download and configure the self-hosted agent on your machine.
  • Start the agent and verify it's online in Azure DevOps.
Create a Personal Access Token (PAT)

Your agent needs a way to prove it belongs to your organization when it first connects. A Personal Access Token (PAT) acts as a one-time authentication credential for this registration step.

  • Return to Azure DevOps at dev.azure.com.
  • Click your profile icon in the top-right corner.
  • Select Personal access tokens from the menu.
  • Click + New Token.
  • Enter AgentSetup as the token name.
  • Leave Organization set to your organization.
  • Set the expiration to 30 days (or your preference).
  • Under Scopes, select Custom defined.
  • Click Show all scopes at the bottom of the window to see the full list.
  • Check Agent Pools and select Read & manage. Make sure all other boxes are cleared.
  • Click Create.
  • Copy the token immediately and save it somewhere safe. You will not be able to see it again.

Why Agent Pools (read, manage)?

This scope gives the PAT just enough permission to register an agent into a pool. The PAT is only used during the initial agent registration. After that, the agent uses a different communication channel.

Can't find Agent Pools in the scope list?

Make sure you clicked Show all scopes at the bottom of the token creation window. Without expanding the list, Agent Pools is hidden.

help me find the Agent Pools scope when creating a PAT in Azure DevOps

Download and configure the agent

Azure DevOps has a pre-built agent package you can download directly from your organization settings. You'll extract it and run an interactive configuration script.

  • In Azure DevOps, click Organization settings (the gear icon at the bottom of the left sidebar).
  • Select Agent pools under Pipelines.
  • Click the Default pool.
  • Click New agent.
  • In the dialog that appears, select Windows as the OS.
  • Click Download to save the agent zip file.

Now extract and configure the agent.

  • Create a folder at C:\agent on your machine.
  • Extract the downloaded zip file into C:\agent.
  • Open PowerShell as Administrator by pressing the Windows key, typing PowerShell, right-clicking Windows PowerShell, and selecting Run as administrator.
  • Navigate to the agent folder by running this command:
cd C:\agent
  • Run the configuration script:
.\config.cmd

The script asks a series of questions. Answer them as follows:

  • Enter server URL: type https://dev.azure.com/ followed by your organization name, then press Enter.
  • Enter authentication type: press Enter to accept the default (PAT).
  • Enter personal access token: paste the PAT you copied earlier, then press Enter.
  • Enter agent pool: press Enter to accept the default (Default).
  • Enter agent name: press Enter to accept the default (your machine name).
  • Enter work folder: press Enter to accept the default (_work).
  • Enter run agent as service? (Y/N): type N and press Enter. You'll run it interactively for this project.

You should see a message saying the agent was added successfully.

Why not run as a service?

Running interactively means you can see the agent's output in real time and stop it easily when you're done. In production, teams run agents as Windows services so they start automatically on boot. For a learning project, interactive mode keeps things simpler.

Seeing a VS30063 authorization error?

This usually means the PAT was not created with the correct scope, or it has expired. Go back to Personal access tokens, delete the old token, and create a new one with Agent Pools (Read & manage) checked.

Also confirm your server URL includes a trailing slash and matches your organization exactly.

help me fix a VS30063 authorization error when configuring a self-hosted agent in Azure DevOps

Start the agent

The agent is configured but not yet running. Starting it makes it listen for pipeline jobs from Azure DevOps.

  • In the same PowerShell window (still inside C:\agent), start the agent by running:
.\run.cmd

You should see output indicating the agent is listening for jobs. Leave this window open.

  • Back in Azure DevOps, go to Organization settings (gear icon at the bottom of the left sidebar).
  • Select Agent pools under Pipelines.
  • Click Default.
  • Click the Agents tab.
  • Verify your agent appears with a green Online status.

What does Online mean?

An agent showing Online means it has successfully connected to Azure DevOps and is ready to pick up pipeline jobs. If it shows Offline, the run.cmd process is not running.

Agent showing as Offline?

Make sure the PowerShell window where you ran .\run.cmd is still open and running. If you closed it, navigate back to C:\agent and run .\run.cmd again.

help me get my self-hosted Azure DevOps agent to show as Online

Your agent is live and ready to execute pipeline jobs. Next up, you'll write the YAML pipeline that tells this agent exactly what to build and test.

Write and Run the YAML Pipeline

Your self-hosted agent is connected and ready to pick up work. Now it's time to give it something to do.

In this step, you'll write a YAML pipeline file that defines your entire build process as code. Every time you push to GitHub, Azure Pipelines reads this file and runs the steps automatically on your self-hosted agent.

In this step, get ready to:
  • Create an azure-pipelines.yml file that defines restore, build, and test steps.
  • Create a pipeline in Azure DevOps connected to your GitHub repository.
  • Trigger the pipeline automatically by pushing a code change.
Create the azure-pipelines.yml file

A YAML pipeline file lives in your repository alongside your code. It tells Azure Pipelines exactly what to do when a build is triggered.

  • Switch back to VS Code with your WeatherApi project open.
  • Create a new file in the root of your project (the same level as WeatherApi.sln) called azure-pipelines.yml.
  • Paste the following code into azure-pipelines.yml:
trigger:
  - main

pool:
  name: 'Default'

variables:
  buildConfiguration: 'Release'

steps:
- task: DotNetCoreCLI@2
  displayName: 'Restore'
  inputs:
    command: 'restore'
    projects: '**/*.csproj'

- task: DotNetCoreCLI@2
  displayName: 'Build'
  inputs:
    command: 'build'
    projects: '**/*.csproj'
    arguments: '--configuration $(buildConfiguration) --no-restore'

- task: DotNetCoreCLI@2
  displayName: 'Test'
  inputs:
    command: 'test'
    projects: '**/*Tests/*.csproj'
    arguments: '--configuration $(buildConfiguration) --no-build'

What does this pipeline do?

  • trigger: - main tells Azure Pipelines to run this pipeline every time code is pushed to the main branch.
  • pool: name: 'Default' directs the pipeline to use your self-hosted agent in the Default pool (the agent you set up in the previous step).
  • variables: buildConfiguration: 'Release' defines a reusable variable so the build compiles in Release mode.
  • The three DotNetCoreCLI@2 tasks handle restoring NuGet packages, building the solution, and running tests. Each task uses the official Azure Pipelines task for .NET commands.
  • Save azure-pipelines.yml.

Why DotNetCoreCLI@2 tasks instead of script commands?

The DotNetCoreCLI@2 task is a pre-packaged Azure Pipelines task that wraps dotnet CLI commands. When you use the test command, it automatically publishes test results to the Azure DevOps UI so you can see pass/fail details without extra configuration.

  • Commit and push the new file to GitHub by running these commands in your terminal:
git add azure-pipelines.yml
git commit -m "Add CI pipeline"
git push
  • You should see the push succeed with no errors in your terminal output.

Push failed or rejected?

  • Make sure you are on the main branch. Run git branch to check.
  • If you see a "no upstream branch" error, run git push -u origin main instead.

Why can't I push to GitHub?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

trigger:
  - main

pool:
  name: 'Default'

variables:
  buildConfiguration: 'Release'

steps:
- task: DotNetCoreCLI@2
  displayName: 'Restore'
  inputs:
    command: 'restore'
    projects: '**/*.csproj'

- task: DotNetCoreCLI@2
  displayName: 'Build'
  inputs:
    command: 'build'
    projects: '**/*.csproj'
    arguments: '--configuration $(buildConfiguration) --no-restore'

- task: DotNetCoreCLI@2
  displayName: 'Test'
  inputs:
    command: 'test'
    projects: '**/*Tests/*.csproj'
    arguments: '--configuration $(buildConfiguration) --no-build'
Create the pipeline in Azure DevOps

Now that your YAML file is in GitHub, you need to tell Azure DevOps where to find it. This connects your repository to Azure Pipelines and kicks off the first build.

  • Return to your Azure DevOps project in the browser.
  • Select Pipelines from the left navigation menu.
  • Click New pipeline (or Create pipeline if this is the first pipeline in the project).
  • On the Where is your code? screen, select GitHub.
  • If prompted, authorize Azure Pipelines to access your GitHub account.
  • Select your WeatherApi repository from the list.
  • On the Configure your pipeline page, select Existing Azure Pipelines YAML file.
  • In the dropdown, select /azure-pipelines.yml as the path, then click Continue.
  • Review the YAML preview. Click Run to save the pipeline and start the first build.

Azure DevOps now queues a build on your self-hosted agent. You should see the pipeline run appear with a status indicator.

  • Click on the running pipeline to watch the live logs.
  • Wait for all three steps (Restore, Build, Test) to complete with green checkmarks.

What should I see?

The pipeline run summary shows each step with its duration and status. Under the test step, you should see your test results published automatically. This happens because the DotNetCoreCLI@2 test command publishes test results by default.

Pipeline stuck on 'Queued'?

  • Check that your self-hosted agent is still running. Switch to the terminal where you ran run.cmd and confirm it shows as listening for jobs.
  • In Azure DevOps, go to Organization Settings then Agent pools then Default and verify your agent shows as Online.
  • If the agent went offline, restart it by running run.cmd again in the agent folder.

Why is my pipeline stuck in the queue?

Trigger the pipeline automatically

The first run was triggered manually when you clicked Run. The real power of CI/CD is that pipelines trigger automatically on every push. Let's prove it works by adding a new test method and pushing the change.

  • In VS Code, open WeatherApi/Calculator.cs.
  • Find the Subtract method and add the following Multiply method below it:
    public int Multiply(int a, int b)
    {
        return a * b;
    }
  • Save Calculator.cs.
  • Open WeatherApi.Tests/CalculatorTests.cs.
  • Find the Subtract_TwoNumbers_ReturnsDifference test method and add the following test below it:
    [Fact]
    public void Multiply_TwoNumbers_ReturnsProduct()
    {
        var result = _calculator.Multiply(3, 7);
        Assert.Equal(21, result);
    }
  • Save CalculatorTests.cs.

What does this test do?

The new test calls Multiply(3, 7) and asserts the result equals 21. When the pipeline runs, this third test will appear in the test results alongside the existing Add and Subtract tests.

  • Verify the new test passes locally by running:
dotnet test
  • You should see 3 tests passed in the output.
  • Commit and push the changes by running:
git add .
git commit -m "Add Multiply method and test"
git push
  • Switch back to Azure DevOps in your browser.
  • Select Pipelines from the left menu.
  • You should see a new pipeline run has been triggered automatically by your push.
  • Click the new run to watch it complete. All three steps should finish with green checkmarks, and the test results should now show 3 passing tests.

Why did the pipeline start on its own?

Your azure-pipelines.yml file includes trigger: - main. Each time you push a change to the main branch, Azure Pipelines detects the push and starts a new run automatically.

Pipeline didn't trigger automatically?

  • Make sure you pushed to the main branch. The trigger only watches main.
  • Confirm the push succeeded in your terminal. If it shows errors, resolve them and push again.
  • In Azure DevOps, check that the pipeline's CI trigger has not been overridden. Click your pipeline, then the three dots menu, then Triggers to verify.

Why isn't my pipeline triggering on push?

Your CI pipeline is fully operational. Every push to main now triggers an automatic build and test cycle. In the Secret Mission, you'll take this further by adding code coverage reporting and a status badge to your GitHub README.

Secret mission

Add Code Coverage and Build Badge

Your pipeline builds and tests automatically. But how do you know how much of your code those tests actually cover? And how do you show the world your pipeline is passing?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on free tiers and local resources, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Self-hosted agent running on your Windows machine (Default pool).
  • Azure DevOps organization and WeatherApi project.
  • Personal Access Token (PAT) for agent authentication.
  • GitHub repository (WeatherApi) with pipeline YAML and source code.
  • Local project files on your Desktop.

Keep everything running

No action needed. Choose this if you are still actively building on this pipeline or plan to extend it soon.

  • Your self-hosted agent continues running in the terminal where you started it. It uses a small amount of memory and CPU while idle.
  • Your Azure DevOps organization, project, and PAT remain active on the free tier at no cost.
  • Your GitHub repository stays available for future pushes.

Pause - I'll come back to this later

Shut down running processes to free up memory while keeping all your files and cloud resources intact for later.

  • In the terminal window where your agent is running, press Ctrl+C to stop the agent. You can restart it later by running run.cmd from the agent folder.
  • Your Azure DevOps organization, pipeline, and GitHub repository remain available at no cost whenever you are ready to return.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each item below to fully clean up.

  • Stop the agent by pressing Ctrl+C in its terminal window.
  • Remove the agent configuration by running the following command from your agent folder:
  • When prompted, enter your PAT to authenticate the removal. The agent is unregistered from the Default pool.
  • Revoke your Personal Access Token. In Azure DevOps, open User settings and select Personal access tokens. Select the token you created, click Revoke, and confirm in the dialog.
  • Delete your Azure DevOps organization. Go to Organization settings and select Overview. Click Delete, enter your organization name, and confirm. You have 28 days to recover it if you change your mind.
  • Delete your GitHub repository. On GitHub, go to your WeatherApi repository, then Settings. Scroll to the bottom and click Delete this repository. Confirm by typing the repository name.
  • Delete your local project files by running:
.\config.cmd remove
cd ~\Desktop
Remove-Item -Recurse -Force WeatherApi

Also delete the agent folder you extracted during setup. Its location depends on where you unzipped it (for example, C:\agent):

Remove-Item -Recurse -Force C:\agent

Nice Work!

Nice Work!

You just built your first CI/CD pipeline from scratch. Every time you push code to GitHub, Azure DevOps automatically restores dependencies, compiles your project, and runs your unit tests.

You've learned how to:

  • Created an ASP.NET Core Web API with a testable Calculator service and xUnit tests.
  • Configured a self-hosted pipeline agent on your Windows machine connected to Azure DevOps.
  • Wrote a YAML pipeline that automatically restores, builds, and tests your .NET code on every push to GitHub.
  • Added code coverage reporting to your pipeline and displayed a build status badge on your GitHub README.

Ready to quiz yourself?