Build a CI/CD Pipeline with Azure DevOps

Automate building and testing a Node.js app with Azure DevOps pipelines.

Introduction

30 Second Summary

Every time a developer pushes code, someone has to build it, test it, and deploy it. Doing this manually is slow, error-prone, and impossible to scale across a team.

In this project, you will build a CI/CD pipeline in Azure DevOps that automatically builds, tests, and packages a Node.js application every time you push code.

What You'll Build

A fully automated YAML pipeline connected to a self-hosted Windows agent that runs your builds instantly with no waiting.

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

  • An Azure DevOps project with a Git repository containing a Node.js app with automated tests.
  • A self-hosted Windows pipeline agent registered to your organization, giving you unlimited build minutes at no cost.
  • A YAML pipeline that triggers on every push to main, runs tests, and publishes build artifacts proving your app was packaged successfully.
  • Secret Mission: Add a multi-stage pipeline with separate Build and Deploy stages that simulates a production deployment.

Are there any prerequisites?

You should have basic familiarity with Git (commits, push, pull). No prior CI/CD experience is required. This project runs on Windows and uses PowerShell for terminal commands.

Set Up Your Azure DevOps Account and Tools

Before you can automate anything, you need a platform for your pipeline to live on and the tools it will work with.

Azure DevOps is Microsoft's platform for running CI/CD pipelines. It stores your code, runs your builds, and publishes your artifacts all in one place. In this step, you'll create your Azure DevOps account, set up a new project, and install Node.js and Git on your local machine.

In this step, get ready to:
  • Create an Azure DevOps organization and project.
  • Install Node.js on your local machine.
  • Install Git for version control.
Create your Azure DevOps organization and project

Azure DevOps organizes work into organizations (your top-level account) and projects (individual workspaces). You'll create one of each to house your pipeline.

  • Open your browser and navigate to https://dev.azure.com/.
  • Click Start free.
  • Sign in with an existing Microsoft account, or click Create one to make a new Microsoft account.
  • Review the Terms of Service and select Continue.
  • When prompted, enter a name for your organization. If the name is taken, add a few numbers to make it unique.
  • Choose a hosting location near you and select Continue.

Azure DevOps now prompts you to create your first project.

  • Enter my-node-pipeline in the Project name field.
  • Set Visibility to Private.
  • Click Create project.

You should see the project welcome page with links to Repos, Pipelines, Boards, and more.

What is an organization vs a project?

An organization is your top-level container in Azure DevOps. It holds all your projects, users, and billing settings.

A project is a workspace inside an organization. Each project gets its own Git repository, pipeline configuration, and work tracking board.

Don't see the Create project button?

Make sure you completed the organization creation step first. You need to be the organization owner to create projects. If you skipped the organization prompt, click the Azure DevOps logo in the top left to open the Projects page, then click New project.

Help me troubleshoot my Azure DevOps project creation

Install Node.js

Your pipeline will build a Node.js application, so you need Node.js installed locally to create and test the app before pushing it to Azure DevOps.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check if Node.js is already installed by running these commands:
node --version
npm --version

✔️ I see version numbers for both

Node.js and npm are ready to use. You can move on to the next substep.

ⓧ Command not found

  • Open your browser and navigate to https://nodejs.org.
  • Download the LTS (Long Term Support) version for your operating system.
  • Run the installer and accept the default settings.
  • Close your terminal completely and reopen it.
  • Run node --version and npm --version again to confirm the installation.

You should now see version numbers printed for both commands.

Why LTS?

The LTS version receives security updates and bug fixes for a longer period. It's the most stable choice for building projects.

Still not seeing a version number?

On Windows, the Node.js installer sometimes fails to update your system PATH. Restart your computer to ensure the PATH changes take effect, then reopen PowerShell and try again.

Help me fix my Node.js installation

Install Git

Git is the version control system that connects your local code to Azure Repos. When you push code changes, Git triggers your pipeline automatically.

  • Check if Git is already installed by running this command in your terminal:
git --version

✔️ I see a version number

Git is already installed and ready to use. You can move on.

ⓧ Command not found

  • Download Git from https://git-scm.com (choose the installer for your operating system).
  • Run the installer and accept the default settings.
  • Close your terminal completely and reopen it.
  • Run git --version again to confirm the installation.

You should now see a version number printed (for example, git version 2.x.x).

Git still not recognized?

Make sure you closed and reopened your terminal after installation. If the problem persists, restart your computer to refresh the system PATH.

Help me troubleshoot my Git installation

Your Azure DevOps project is live and your developer tools are ready. Next up, you'll build the Node.js application that your pipeline will automatically build and test.

Create Your Node.js Application

Every CI/CD pipeline needs an application to build and test. Before you configure the pipeline itself, you need working code that the pipeline can run against.

In this step, you'll create a simple Node.js application with math utility functions, automated tests, and a build script. This gives your pipeline something meaningful to build, test, and package.

In this step, get ready to:
  • Initialize a Node.js project folder.
  • Create application code, tests, and a build script.
  • Verify tests pass locally before pushing to the pipeline.
Initialize the project

You need a dedicated folder for your application code and a package.json file that defines your project's metadata and dependencies.

  • Create your project folder and initialize it by running these commands in PowerShell:
mkdir C:\projects\my-node-app
cd C:\projects\my-node-app
npm init -y

What do these commands do?

  • mkdir creates a new folder at C:\projects\my-node-app.
  • cd moves your terminal into that folder.
  • npm init -y generates a default package.json with sensible defaults, skipping all the interactive prompts.
  • Confirm the initialization succeeded. You should see a message showing package.json was written successfully.

Seeing 'mkdir is not recognized'?

Make sure you are using PowerShell, not Command Prompt. PowerShell supports mkdir natively.

Help me open PowerShell on Windows.

Create the application and test files

Your application needs three files: a module with functions to test, a test file that verifies those functions, and a build script that packages your app.

  • Press the Windows key to open your search bar.
  • Type VS Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to C:\projects\my-node-app and click Select Folder.

Trust the authors

If VS Code asks whether you trust the authors of the files in this folder, click Yes, I trust the authors.

  • Create a new file called index.js by clicking the new file icon in the Explorer sidebar.
  • Paste the following code into index.js:
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { add, subtract, multiply };

What does this code do?

This module defines three simple math functions: add, subtract, and multiply.

The module.exports line makes these functions available to other files (like your test file). The functions are intentionally simple because the focus of this project is the pipeline, not the application logic.

  • Save index.js with Ctrl+S.

✔️ Awesome, I've got everything!

Great. Make sure you've saved index.js before moving on.

ⓧ I'd like to double check the full code

function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { add, subtract, multiply };
  • Create a new file called test.js in the same folder.
  • Paste the following code into test.js:
const { add, subtract, multiply } = require('./index');

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

test('subtracts 5 - 3 to equal 2', () => {
  expect(subtract(5, 3)).toBe(2);
});

test('multiplies 3 * 4 to equal 12', () => {
  expect(multiply(3, 4)).toBe(12);
});

What does this code do?

This file imports the three functions from index.js and runs automated checks against them.

Each test() block calls a function with known inputs and checks the output matches the expected value using expect().toBe(). When your pipeline runs these tests, a failure here stops the build and alerts you immediately.

  • Save test.js with Ctrl+S.

✔️ Awesome, I've got everything!

Great. Make sure you've saved test.js before moving on.

ⓧ I'd like to double check the full code

const { add, subtract, multiply } = require('./index');

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

test('subtracts 5 - 3 to equal 2', () => {
  expect(subtract(5, 3)).toBe(2);
});

test('multiplies 3 * 4 to equal 12', () => {
  expect(multiply(3, 4)).toBe(12);
});
  • Create a new file called build.js in the same folder.
  • Paste the following code into build.js:
const fs = require('fs');
const path = require('path');

const buildDir = path.join(__dirname, 'build');

if (!fs.existsSync(buildDir)) {
  fs.mkdirSync(buildDir);
}

fs.copyFileSync(
  path.join(__dirname, 'index.js'),
  path.join(buildDir, 'index.js')
);

fs.copyFileSync(
  path.join(__dirname, 'package.json'),
  path.join(buildDir, 'package.json')
);

console.log('Build completed successfully!');
console.log('Output directory:', buildDir);

What does this code do?

This script simulates a production build. It creates a build folder and copies index.js and package.json into it.

Your pipeline will run this script to produce a packaged artifact. In a real project, the build step might compile TypeScript, bundle assets, or minify code.

  • Save build.js with Ctrl+S.

✔️ Awesome, I've got everything!

Great. Make sure you've saved build.js before moving on.

ⓧ I'd like to double check the full code

const fs = require('fs');
const path = require('path');

const buildDir = path.join(__dirname, 'build');

if (!fs.existsSync(buildDir)) {
  fs.mkdirSync(buildDir);
}

fs.copyFileSync(
  path.join(__dirname, 'index.js'),
  path.join(buildDir, 'index.js')
);

fs.copyFileSync(
  path.join(__dirname, 'package.json'),
  path.join(buildDir, 'package.json')
);

console.log('Build completed successfully!');
console.log('Output directory:', buildDir);
  • Install the Jest testing framework by running this command in your PowerShell terminal:
npm install --save-dev jest@29.7.0

What does this command do?

This installs Jest version 29.7.0 as a development dependency. The --save-dev flag means Jest is only needed during development and testing, not in production.

  • You should see output confirming packages were added. A node_modules folder now appears in your project.

Seeing 'npm is not recognized'?

This means Node.js is not in your system PATH. Close and reopen PowerShell after installing Node.js. If the issue persists, reinstall Node.js from https://nodejs.org and make sure the option to add to PATH is checked.

Help me fix npm not being recognized.

Now you need to update package.json so it knows how to run your tests and build script.

  • Open package.json in VS Code by clicking on it in the Explorer sidebar.
  • Find the "scripts" section. It currently looks like this:
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
}
  • Replace the entire "scripts" block with this updated version:
"scripts": {
  "test": "jest",
  "build": "node build.js"
}
  • Also update the "description" field from an empty string to:
"description": "A simple Node.js app for CI/CD pipeline demo"

What did you change?

The "test": "jest" script tells npm to run Jest when you type npm test. The "build": "node build.js" script runs your build script when you type npm run build.

Your pipeline will use these same scripts to build and test your code automatically.

  • Save package.json with Ctrl+S.

✔️ Awesome, I've got everything!

Great. Double check you've saved package.json before moving on.

ⓧ I'd like to double check the full code

Your full package.json should look like this:

{
  "name": "my-node-app",
  "version": "1.0.0",
  "description": "A simple Node.js app for CI/CD pipeline demo",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "build": "node build.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "29.7.0"
  }
}
Verify tests pass locally

Before your pipeline can test this code automatically, you should confirm everything works on your own machine first.

  • Run your tests by entering this command in PowerShell (make sure you're still in C:\projects\my-node-app):
npm test
  • You should see output showing all three tests passing with green text. Look for Tests: 3 passed, 3 total at the bottom of the output.

Seeing test failures?

Check that your index.js exports all three functions with module.exports = { add, subtract, multiply }. A missing export causes an import error in test.js.

Also confirm that package.json has "test": "jest" in the scripts section. If it still says the default error message, Jest is not being called.

Help me debug my failing tests.

Your application is tested and ready to go. Next up, you'll push this code to Azure Repos so your pipeline can access it automatically.

Push Your Code to Azure Repos

Your app is built, your tests pass, and everything works locally. Now you need to get this code into a place where your pipeline can access it.

Azure Repos is the Git hosting service built into Azure DevOps. Pushing your code there connects it to the pipeline engine you will configure in the next step.

In this step, get ready to:
  • Initialize a Git repository in your project folder.
  • Connect it to your Azure Repos remote.
  • Push your code to the main branch.
Initialize Git and connect to Azure Repos

Before you can push code anywhere, your project folder needs to be a Git repository. You also need to tell Git where the remote copy of your code lives.

  • Initialize a Git repository and set the default branch to main by running these commands:
cd C:\projects\my-node-app
git init
git branch -M main

What do these commands do?

  • git init creates a hidden .git folder that turns your project into a Git repository.
  • git branch -M main renames the default branch to main. Some Git installations default to master, so this ensures consistency with Azure Repos.
  • Connect your local repository to Azure Repos by adding the remote:
git remote add origin https://dev.azure.com/[[YOURORG="your-organization-name"]]/my-node-pipeline/_git/my-node-pipeline

What is a remote?

A remote is a URL that points to a copy of your repository hosted somewhere else. The name origin is a convention for the primary remote. When you push, Git sends your commits to this URL.

  • Verify the remote was added correctly by running:
git remote -v
  • You should see your Azure DevOps URL listed twice (once for fetch, once for push).

Not seeing the URL?

  • Make sure you ran git init first. The git remote add command only works inside an initialized Git repository.
  • Double-check that you replaced the organization name in the URL with your actual Azure DevOps organization name.
  • help me fix my Git remote URL
Create a .gitignore and push your code

Some folders should never be committed to your repository. The node_modules/ folder contains thousands of downloaded dependency files, and build/ contains generated output. A .gitignore file tells Git to skip these.

  • Create the .gitignore file inside C:\projects\my-node-app by running this command:
@"
node_modules/
build/
"@ | Set-Content .gitignore

What does this command do?

This PowerShell here-string creates a file called .gitignore with two entries. Any path matching these patterns will be invisible to Git, so your commits stay clean and lightweight.

  • Confirm the file was created by running:
Get-Content .gitignore
  • You should see node_modules/ and build/ printed in the output.

Seeing an empty file or an error?

  • Make sure the "@ closing marker is at the very start of its line with no leading spaces.
  • If you prefer, create the file manually in your editor. Create a new file called .gitignore and type the two lines yourself.
  • help me create my .gitignore file

What about authentication?

When you push for the first time, Git will ask you to sign in. A browser window may open asking you to authenticate with your Microsoft account. Allow this to proceed.

If a browser window does not appear, Git may prompt you in the terminal for credentials. Enter your Azure DevOps username and a Personal Access Token (you will create one in the next step).

  • Stage all your files, commit them, and push to Azure Repos by running these commands:
git add .
git commit -m "Initial commit - Node.js app with tests"
git push -u origin main

What do these commands do?

  • git add . stages every file in the folder (except those ignored by .gitignore) for your next commit.
  • git commit -m "..." saves a snapshot of your staged files with a descriptive message.
  • git push -u origin main uploads your commit to the main branch on Azure Repos. The -u flag links your local branch to the remote so future pushes only need git push.
  • After the push completes, you should see output mentioning the branch main and the remote URL.
  • Verify your code arrived by opening your Azure DevOps project in the browser and clicking Repos in the left sidebar.
  • You should see index.js, test.js, build.js, package.json, and .gitignore listed in the file tree.

Push failed or authentication error?

  • If you see a permission error, confirm you are signed in with the same Microsoft account you used to create the Azure DevOps organization.
  • If Git says the branch main does not exist, make sure you ran git branch -M main after git init.
  • If you see "repository not found", double-check the organization name and project name in your remote URL match exactly what you created in Azure DevOps.
  • help me fix my git push to Azure Repos

✔️ Awesome, I've got everything!

Great. Double check you see all five files in Azure Repos before moving on.

ⓧ I'd like to double check the full code

Here is the complete .gitignore file for comparison:

node_modules/
build/

Your code is now in Azure Repos and ready to be picked up by a pipeline. Next up, you will set up a self-hosted agent on your machine so your pipeline has something to run on.

Set Up a Self-Hosted Agent

Your code is now in Azure Repos. The next step is getting a pipeline to build it automatically.

Every pipeline needs an agent to run on. An agent is a machine that listens for pipeline jobs and executes them. You will set up a self-hosted agent on your own Windows machine, which gives you unlimited pipeline minutes at no cost.

Why a self-hosted agent instead of Microsoft-hosted?

Azure DevOps offers Microsoft-hosted agents, but the free grant of parallel jobs for new organizations is temporarily disabled. You would need to submit a request and wait several business days before you could use one.

A self-hosted agent runs immediately on your machine with unlimited build minutes. Many organizations use self-hosted agents in production for security and performance reasons, so this is a valuable skill to learn.

In this step, get ready to:
  • Create a Personal Access Token for agent authentication.
  • Download and configure the self-hosted agent on your machine.
  • Start the agent and confirm it is listening for jobs.
Create a Personal Access Token

The agent needs a way to prove its identity to Azure DevOps. A Personal Access Token (PAT) acts as a password that grants the agent permission to register itself in your organization's agent pool.

  • Return to Azure DevOps in your browser.
  • Click your user icon in the top-right corner.
  • Select Personal access tokens.
  • Click + New Token.
  • In the Name field, enter pipeline-agent.
  • Set Expiration to 30 days.
  • Under Scopes, find Agent Pools and select Read & manage.
  • Click Create.
  • Copy the token immediately and save it somewhere safe (e.g. a temporary text file). It will not be shown again.

What is a Personal Access Token?

A PAT is an alternative to your password that you can scope to specific permissions and set to expire. Here you scoped it to Agent Pools (Read & manage) so the token can only register and manage agents. It cannot access your code or other resources.

Don't see the + New Token button?

Your organization may have policies restricting PAT creation. Check Organization Settings > Policies and ensure the Restrict personal access token creation policy is disabled or your user is on the allowlist.

Help me troubleshoot PAT creation in Azure DevOps.

Download and configure the agent

Now you will download the agent software from Azure DevOps and configure it to connect to your organization.

  • In Azure DevOps, click the gear icon in the bottom-left corner to open Project Settings.
  • In the left sidebar under Pipelines, select Agent pools.
  • Click the Default pool.
  • Select the Agents tab, then click New agent.
  • In the dialog, choose Windows.
  • Select x64 on the left pane, then click Download.
  • Once downloaded, create a folder at C:\agents and extract the ZIP contents into it.

Avoid spaces in the path

Make sure the path to your agent folder contains no spaces. Tools and scripts do not always escape spaces correctly. C:\agents is the recommended path. Do not extract into your Downloads folder.

  • Open an elevated (Administrator) PowerShell window by pressing the Windows key, typing PowerShell, right-clicking Windows PowerShell, and selecting Run as administrator.
  • Navigate to the agent folder and run the configuration command:
cd C:\agents
.\config.cmd

What does config.cmd do?

This interactive script registers your agent with Azure DevOps. It asks a series of questions to establish the connection between your machine and your organization's agent pool.

The script will prompt you with several questions. Enter the following responses:

  • Server URL: enter your organization URL in the format below:
  • Authentication type: enter PAT.
  • Token: paste the Personal Access Token you copied earlier.
  • Agent pool: press Enter to accept the default (Default).
  • Agent name: press Enter to accept the default.
  • Work folder: press Enter to accept the default (_work).
https://dev.azure.com/[[AZURE_ORG="your-organization-name"]]

Getting an authentication error?

Double-check that you copied the full PAT (it is a long string of characters). Also confirm the Server URL matches your organization exactly. You can find it in your browser address bar when viewing Azure DevOps.

Help me fix my agent configuration authentication error.

Start the agent

With the agent configured, you can now start it so it begins listening for pipeline jobs from Azure DevOps.

  • Start the agent interactively by running this command:
.\run.cmd

You should see output that ends with "Listening for Jobs" in the terminal. Leave this PowerShell window open.

What does Listening for Jobs mean?

The agent is now connected to Azure DevOps and waiting for pipeline runs. When you trigger a pipeline in the next step, Azure DevOps sends the job to this agent. The agent executes the pipeline steps on your machine and reports results back.

Agent not starting or showing errors?

Make sure you are still in the C:\agents directory and that you ran the configuration step (.\config.cmd) successfully first. If you see a .NET-related error, you do not need to install .NET separately. The agent bundles all its dependencies.

Help me troubleshoot my Azure DevOps self-hosted agent startup.

Your self-hosted agent is live and waiting for work. Next up, you will create the YAML pipeline that tells the agent exactly what to build.

Create Your YAML Pipeline

Your self-hosted agent is running and waiting for work. Now it is time to give it something to do.

A YAML pipeline is a file in your repository that tells Azure Pipelines exactly what steps to run every time you push code. In this step, you will write that file, push it to Azure Repos, and watch your agent build, test, and package your app automatically.

In this step, get ready to:
  • Write the pipeline YAML file that defines your build process.
  • Push the file to Azure Repos to trigger your first pipeline run.
  • Configure and run the pipeline in the Azure DevOps portal.
Create the pipeline file

The pipeline file lives at the root of your repository. It defines every action the agent performs: installing Node.js, grabbing dependencies, running tests, building the app, and publishing the output as an artifact.

  • In your code editor, create a new file called azure-pipelines.yml in the root of your C:\projects\my-node-app folder.
  • Paste the following YAML into the file:
trigger:
  branches:
    include:
      - main

pool:
  name: Default

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'
    displayName: 'Install Node.js'

  - script: npm install
    displayName: 'Install dependencies'

  - script: npm test
    displayName: 'Run tests'

  - script: npm run build
    displayName: 'Build application'

  - task: PublishPipelineArtifact@1
    inputs:
      targetPath: '$(System.DefaultWorkingDirectory)/build'
      artifact: 'node-app-build'
      publishLocation: 'pipeline'
    displayName: 'Publish build artifact'

What does this pipeline do?

  • trigger tells Azure Pipelines to run this pipeline whenever code is pushed to the main branch.
  • pool: name: Default routes the pipeline to your self-hosted agent in the Default pool.
  • NodeTool@0 installs the specified version of Node.js on the agent, ensuring a consistent runtime regardless of what is installed on your machine.
  • The three script steps install dependencies, run your Jest tests, and execute the build script.
  • PublishPipelineArtifact@1 packages the contents of the build folder and publishes them as a downloadable artifact named node-app-build.
  • Save azure-pipelines.yml.

YAML indentation matters

YAML uses spaces (not tabs) for indentation. Make sure each nested level uses exactly two spaces. A single misaligned space will cause the pipeline to fail validation.

Push the pipeline file to Azure Repos

Your pipeline file needs to be in Azure Repos before Azure DevOps can see it. Committing and pushing the file also triggers your first pipeline run automatically.

  • Switch back to your PowerShell window that is open in C:\projects\my-node-app.
  • Stage, commit, and push the pipeline file by running these commands:
git add azure-pipelines.yml
git commit -m "Add CI/CD pipeline"
git push

What does this do?

These commands add the new file to Git's staging area, create a commit with a descriptive message, and push the commit to the main branch on Azure Repos. Because the pipeline triggers on pushes to main, this push kicks off the first pipeline run.

  • You should see output confirming the push succeeded.

Push rejected or authentication error?

  • If prompted for credentials, enter your Azure DevOps username and a Personal Access Token with Code (Read & Write) scope.
  • If the push is rejected because of a non-fast-forward error, run git pull --rebase origin main first, then try pushing again.

Help me troubleshoot my git push to Azure Repos

Configure and run the pipeline in Azure DevOps

The YAML file is in your repo, but Azure DevOps needs to know it exists. You will now point the portal at your file and kick off the first run.

  • In your browser, return to your my-node-pipeline project in Azure DevOps.
  • Select Pipelines in the left navigation menu.
  • Click New pipeline (or Create pipeline if this is the first pipeline in the project).
  • Select Azure Repos Git as the code source.
  • Select your my-node-pipeline repository.
  • On the Configure your pipeline page, select Existing Azure Pipelines YAML file.
  • In the path dropdown, select /azure-pipelines.yml and click Continue.
  • Review the YAML preview, then click Run.

Azure DevOps queues the pipeline run and sends the job to your self-hosted agent. Switch to the PowerShell window running your agent and you will see it pick up the job.

What should I see?

In the Azure DevOps portal, the pipeline run page shows each step executing in real time. You should see green checkmarks next to Install Node.js, Install dependencies, Run tests, Build application, and Publish build artifact.

In the agent PowerShell window, you will see log output as each step runs. After the run completes, the agent returns to the "Listening for Jobs" state.

Pipeline stuck on "Queued" or not starting?

  • Make sure your agent window still shows "Listening for Jobs". If it was closed, restart the agent with .\run.cmd from C:\agents.
  • Confirm the YAML file specifies pool: name: Default. A typo in the pool name means Azure DevOps cannot find your agent.
  • Check that your agent is online under Project Settings > Agent pools > Default > Agents. The agent should show a green icon.

Help me troubleshoot my Azure DevOps pipeline that is stuck in the queued state

✔️ Awesome, I've got everything!

Great. Double check you have saved azure-pipelines.yml and that your pipeline run shows green checkmarks on every step.

ⓧ I'd like to double check the full code

Here is the complete azure-pipelines.yml file:

trigger:
  branches:
    include:
      - main

pool:
  name: Default

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'
    displayName: 'Install Node.js'

  - script: npm install
    displayName: 'Install dependencies'

  - script: npm test
    displayName: 'Run tests'

  - script: npm run build
    displayName: 'Build application'

  - task: PublishPipelineArtifact@1
    inputs:
      targetPath: '$(System.DefaultWorkingDirectory)/build'
      artifact: 'node-app-build'
      publishLocation: 'pipeline'
    displayName: 'Publish build artifact'

Your pipeline is live and building on every push. Next up, you will verify the build artifacts and trigger a fresh run by making a code change.

Verify Your Pipeline and Trigger a New Build

Your pipeline ran successfully for the first time in the previous step. Now it is time to confirm everything worked as expected and prove that your pipeline truly automates on every push.

In this step, you will review pipeline logs to understand what happened during the build, download the artifact your pipeline produced, and trigger a second run by pushing a code change.

In this step, get ready to:
  • Review your pipeline run logs in Azure DevOps.
  • Download and verify your build artifact.
  • Trigger a new pipeline run by pushing a code change.
Review the pipeline run

Every pipeline run in Azure DevOps produces detailed logs for each step. These logs show you exactly what commands ran, what output they produced, and whether each step passed or failed.

  • In Azure DevOps, click Pipelines in the left sidebar.
  • Click on your pipeline run (the most recent entry with a green checkmark).
  • Click on the job name to expand the step-by-step logs.
  • Click on each step (Install Node.js, Install dependencies, Run tests, Build application, Publish build artifact) to view its logs.

What should I see in the logs?

In the Run tests step, you should see Jest reporting all three tests passing. In the Build application step, you should see the messages Build completed successfully! and the output directory path.

Each step shows a green checkmark if it passed. If any step had failed, the pipeline would have stopped at that point and shown a red X.

Download and verify artifacts

Your pipeline published a build artifact containing the packaged application files. Downloading and inspecting this artifact proves your pipeline produced real, usable output.

  • Navigate back to your pipeline run and click the Summary tab.
  • In the Related section, click the published artifact named node-app-build.
  • Click the download icon (or right-click and choose Download artifacts) to download the artifact as a ZIP file.
  • Extract the ZIP and confirm it contains index.js and package.json.

What is a build artifact?

A build artifact is the packaged output of your pipeline. It contains only the files needed to run your application in production. Your build.js script copied index.js and package.json into a build folder, and the pipeline published that folder as the artifact.

In a real production workflow, a deployment stage would pick up this artifact and deploy it to a server.

Don't see the artifact in the Related section?

Make sure you are on the Summary tab of the pipeline run, not the Jobs tab. The artifact link appears in the Related section below the run summary. If the Publish build artifact step failed in the logs, your pipeline did not produce an artifact.

Help me find my build artifact in Azure DevOps.

Trigger a new build with a code change

The real power of CI/CD is automation. Your pipeline is configured to trigger on every push to the main branch. Pushing a code change will prove that this trigger works.

  • In your code editor, open test.js inside your C:\projects\my-node-app folder.
  • Add a new test at the bottom of the file, below the existing multiplies 3 * 4 test:
test('adds 2 + 3 to equal 5', () => {
  expect(add(2, 3)).toBe(5);
});

What does this test do?

This test verifies the add function with different inputs (2 and 3 instead of 1 and 2). Adding a new test is the simplest way to create a meaningful code change that the pipeline can pick up and verify.

  • Save test.js.
  • Commit and push your change by running these commands in PowerShell:
git add .
git commit -m "Add another test case"
git push
  • Switch back to Azure DevOps in your browser and click Pipelines in the left sidebar.
  • You should see a new pipeline run appear within a few seconds, triggered automatically by your push.
  • Wait for the run to complete with a green checkmark.

Why did the pipeline run automatically?

Your azure-pipelines.yml file includes a trigger section that watches the main branch. Every time you push commits to main, Azure DevOps automatically starts a new pipeline run. This is the core of Continuous Integration. Code changes are automatically built and tested without any manual intervention.

Pipeline not triggering automatically?

Make sure you pushed to the main branch (not a different branch). Check that your azure-pipelines.yml trigger section includes main under branches > include. Also confirm that your self-hosted agent is still running (the PowerShell window should still say Listening for Jobs).

Help me figure out why my pipeline did not trigger after pushing.

You have now confirmed that your pipeline automatically builds, tests, and packages your application on every push. This is a fully working CI/CD pipeline. Next up, tackle the Secret Mission to add a multi-stage deployment pipeline.

Secret mission

Add a Multi-Stage Deploy Pipeline

Your pipeline builds and tests your app, but what about deploying it? Real-world pipelines separate build and deploy into distinct stages. Upgrade your pipeline to simulate a production deployment.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses Azure DevOps free tier with a self-hosted agent, so there are no ongoing costs.

Resources you used:

  • Azure DevOps organization and project (my-node-pipeline).
  • Self-hosted agent configured at C:\agents.
  • Local project files at C:\projects\my-node-app.
  • Local deployment folder at C:\deployments\my-node-app.

Keep everything running

No action needed. Choose this if you're still actively building or experimenting with pipelines.

  • Your self-hosted agent uses minimal resources when idle.
  • Azure DevOps free tier includes unlimited self-hosted agent minutes, so your project costs nothing to keep.
  • Node.js and Git remain installed on your machine for future projects.

Pause - I'll come back to this later

Stop your self-hosted agent to free up system resources. Your Azure DevOps project and local files stay intact.

  • In the PowerShell window where your agent is running, press Ctrl+C to stop the agent.
  • To restart later, open PowerShell and run:
cd C:\agents
.\run.cmd

Your Azure DevOps project, local files, and deployment folder all remain untouched until you return.

Delete - I don't want to use this again

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

  • Stop the agent by pressing Ctrl+C in the PowerShell window where it is running.
  • Remove the agent registration by running the following in an elevated PowerShell window:
cd C:\agents
.\config.cmd remove
  • When prompted, enter your Personal Access Token to confirm the removal.
  • Delete the agent folder by running:
Remove-Item -Recurse -Force C:\agents
  • Delete your local project files by running:
Remove-Item -Recurse -Force C:\projects\my-node-app
  • Delete the local deployment folder by running:
Remove-Item -Recurse -Force C:\deployments\my-node-app
  • Delete your Azure DevOps project from the web portal:
  • Sign in to your organization at https://dev.azure.com/.
  • Click Organization settings in the bottom-left sidebar.
  • Select Projects from the left menu.
  • Find my-node-pipeline, click the ellipsis (...) next to it, and select Delete.
  • Confirm by typing the project name and clicking Delete.

Nice Work!

Nice Work!

You just built a complete CI/CD pipeline with Azure DevOps. Every time you push code, your pipeline automatically builds, tests, and packages your application without any manual intervention.

You've learned how to:

  • Configure a self-hosted agent on your Windows machine, giving you unlimited pipeline minutes at no cost.
  • Author a YAML pipeline that automatically builds, tests, and packages a Node.js app on every push to the main branch.
  • Publish build artifacts that prove your pipeline successfully compiled and packaged your application end-to-end.
  • Added a multi-stage pipeline with separate Build and Deploy stages, simulating a production deployment workflow.

Ready to quiz yourself?