Build a CI/CD Pipeline with Azure DevOps
Build a multi-stage YAML pipeline that auto-builds and tests a Node.js app.
Introduction
30 Second Summary
Every time a developer pushes code, something could break. Professional teams solve this by automating their build and test process so bugs are caught before they ever reach users.
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 multi-stage YAML pipeline in Azure DevOps that triggers on every commit to your main branch, installs dependencies, runs unit tests, and publishes build artifacts.
By the end of this project, you'll have:
- An Azure DevOps organization and project configured for automated pipelines.
- A self-hosted build agent running on your machine that executes pipeline jobs.
- A multi-stage YAML pipeline with Build, Test, and Publish stages that triggers on every code push.
- Secret Mission: Add a deployment stage with environment approvals, simulating a real production release workflow.
Are there any prerequisites?
No prior experience is required. This guide includes full setup instructions for all required tools including Node.js, Git, and VS Code.
Set Up Your Azure DevOps Account and Tools
Before you can build a pipeline, you need the tools that make it possible. Your pipeline will build and test a Node.js application, so you need Node.js installed locally to create the app.
In this step, you'll install Node.js, Git, and VS Code on your machine. Then you'll create your Azure DevOps organization, which is where your pipeline will live.
In this step, get ready to:
- Install Node.js and Git on your machine.
- Install VS Code with the Azure Pipelines extension.
- Create your Azure DevOps organization.
Install Node.js and Git
Node.js is the runtime your application will use. Git is the version control tool that tracks your code changes and pushes them to Azure Repos.
- Open your browser and go to https://nodejs.org.
- Download the LTS version (this includes npm).
- Run the installer and accept the default options.
- Next, go to https://git-scm.com.
- Download the installer for your operating system.
- Run the installer and accept the default options.
Once both installations finish, verify they worked.
- Press 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 your Node.js version by running this command:
node --version
✔️ I see a version number
Node.js is installed and ready to go.
ⓧ Command not found
The installer may not have added Node.js to your system PATH.
- Close your terminal completely and open a new one.
- Run node --version again.
- If it still fails, restart your computer and try once more.
- If it still does not work, re-run the Node.js installer from https://nodejs.org and make sure you check the option to add to PATH.
- Check your npm version by running:
npm --version
- You should see a version number printed. npm is bundled with Node.js, so if Node.js installed correctly, npm will too.
- Check your Git version by running:
git --version
✔️ I see a version number
Git is installed and ready to go.
ⓧ Command not found
The installer may not have added Git to your system PATH.
- Close your terminal completely and open a new one.
- Run git --version again.
- If it still fails, restart your computer and try once more.
- If it still does not work, re-run the Git installer from https://git-scm.com and make sure the option to add Git to PATH is selected.
Install VS Code
VS Code is the code editor you'll use to write your application code and your pipeline YAML file.
- Open your browser and go to https://code.visualstudio.com.
- Download and install VS Code for your operating system.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
Now install the Azure Pipelines extension for YAML syntax highlighting.
- Press Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows) to open the Extensions panel.
- Type Azure Pipelines in the search bar.
- Find the extension published by Microsoft and click Install.
What does this extension do?
The Azure Pipelines extension gives you syntax highlighting and autocompletion when editing azure-pipelines.yml files. This makes it easier to spot typos in your pipeline configuration before you push it.
Create your Azure DevOps organization
An Azure DevOps organization is the top-level container for all your projects, repositories, and pipelines. You need one before you can create a project or set up a pipeline.
- Open your browser and go to https://dev.azure.com.
- Click Start free.
- Sign in with a Microsoft account. If you do not have one, create a free account when prompted.
Do I need a credit card?
No. Azure DevOps does not require a credit card to get started. The free tier includes 1 self-hosted CI/CD concurrent job with unlimited minutes, which is what you'll use in this project.
- After signing in, Azure DevOps asks you to create an organization.
- Enter a name for your organization (for example, your-name-devops).
- Choose a region close to you from the dropdown.
- Click Continue to create the organization.
Your organization URL will be https://dev.azure.com/ followed by the name you chose. You should now see the Azure DevOps home page for your new organization.
Don't see the organization creation page?
If you already had a Microsoft account with an existing Azure DevOps organization, you may be taken directly to that organization instead. Look for a link or option to create a new organization in the left sidebar or organization settings.
help me find where to create a new Azure DevOps organization
Your tools are installed and your Azure DevOps organization is live. Next up, you'll create a project inside this organization and push a Node.js application to its repository.
Create Your Project and Repository
Your tools are installed and your Azure DevOps organization is live. Now it's time to create the project that your pipeline will automate.
In this step, you'll create an Azure DevOps project, set up a Git repository, and populate it with a Node.js application that includes automated tests.
In this step, get ready to:
- Create a new Azure DevOps project.
- Build a Node.js application with unit tests.
- Push your code to Azure Repos.
Create a new Azure DevOps project
A project in Azure DevOps is a container for your code, pipelines, boards, and test plans. You need one before you can store any code or run any pipelines.
- Open your browser and go to https://dev.azure.com.
- Sign in with the Microsoft account you used in Step 1.
- Select New project in the top-right corner.
- Enter node-pipeline-demo as the project name.
- Set Visibility to Private.
- Under Version control, select Git.
- Click Create.
What is an Azure DevOps project?
A project groups together your repositories, pipelines, boards, and test plans into one workspace. Everything your CI/CD pipeline needs lives inside this single project.
Azure DevOps displays the project welcome page after creation.
Initialize the repository with a Node.js app
Your project comes with an empty Git repository called Azure Repos. You'll clone it to your computer, then add a Node.js app with tests that the pipeline can build and verify.
- In your project, click Repos in the left sidebar.
- Click Clone in the upper-right corner of the Files view.
- Copy the HTTPS clone URL from the popup.
What is the clone URL?
The clone URL uniquely identifies your remote repository. It follows the format https://dev.azure.com/{org}/{project}/_git/{repo}. You'll paste this into your terminal to download a local copy.
- Open a terminal in VS Code by pressing Ctrl+` (backtick) or going to Terminal > New Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone the repository by running this command:
git clone [[REPO_URL="the HTTPS clone URL you copied from Azure Repos"]]
- Move into the cloned project folder by running:
cd node-pipeline-demo
What did git clone do?
Git downloaded a copy of the remote repository to your Desktop. The node-pipeline-demo folder is now linked to Azure Repos, so you can push changes back to the server.
- Confirm the clone worked by running:
ls
The folder may appear empty (or show only a hidden .git folder). That's expected for a brand-new repository.
Clone failing with an authentication error?
Git Credential Manager should prompt you to sign in with your Microsoft account. If you see a 401 or authentication failure, make sure you're signed into the same Microsoft account you used to create the organization.
help me fix a git clone authentication error with Azure Repos
- Open the cloned folder in VS Code by running:
code .
VS Code opens with the node-pipeline-demo folder loaded in the Explorer sidebar.
- Scaffold a package.json by running this command in the VS Code terminal:
npm init -y
This creates a starter package.json with defaults. You'll replace its contents with your project configuration next.
- In the Explorer sidebar, click package.json to open it.
- Select all the contents and replace them with the following:
{
"name": "node-pipeline-demo",
"version": "1.0.0",
"description": "A Node.js app for learning Azure DevOps CI/CD pipelines",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "jest",
"build": "node build.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "4.21.2"
},
"devDependencies": {
"jest": "29.7.0"
}
}
What does this package.json do?
This file defines your project's dependencies and scripts. Express is a web framework your app uses to serve HTTP responses. Jest is a testing framework that runs your unit tests.
The three scripts tell npm what to do when you run npm start, npm test, or npm run build. Your pipeline will use these same scripts to automate the workflow.
- Save package.json.
- Install the dependencies by running:
npm install
You should see npm download packages. A node_modules folder appears in your Explorer sidebar when it finishes.
npm install failing?
Make sure you're running the command inside the node-pipeline-demo folder. Check that your package.json is valid JSON with no trailing commas or missing brackets.
help me fix npm install errors in my project
- Create a new file called app.js by right-clicking in the Explorer sidebar and selecting New File.
- Paste the following code into app.js:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Root route returns a JSON greeting
app.get('/', (req, res) => {
res.json({ message: 'Hello from Azure DevOps Pipeline!', status: 'running' });
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Utility functions that the pipeline will test
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
// Export for testing
module.exports = { app, add, multiply };
// Start the server only when run directly
if (require.main === module) {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
}
What does this code do?
This file creates a simple Express web server with two routes: a root route that returns a JSON greeting and a health check endpoint.
The add and multiply functions are utility functions exported for testing. The module.exports line makes them available to your test file. The if (require.main === module) block ensures the server only starts when you run the file directly, not when it's imported by tests.
- Save app.js.
You won't see a change yet. The next file writes tests that verify these functions work correctly.
- Create a new file called app.test.js by right-clicking in the Explorer sidebar and selecting New File.
- Paste the following code into app.test.js:
const { add, multiply } = require('./app');
// Test suite for the math utility functions
describe('Math functions', () => {
test('add returns the sum of two numbers', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test('multiply returns the product of two numbers', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(-1, 5)).toBe(-5);
expect(multiply(0, 100)).toBe(0);
});
});
What does this test file do?
This file imports the add and multiply functions from app.js and runs assertions against them. Jest checks that each function returns the expected result for several inputs.
When your pipeline runs npm test, Jest executes this file. If any assertion fails, the pipeline marks the build as failed. This is the "CI" in CI/CD.
- Save app.test.js.
- Verify the tests pass by running:
npm test
You should see output showing two passing tests with green checkmarks.
Tests failing?
Make sure the file is named exactly app.test.js (Jest looks for files matching *.test.js by default). Also check that app.js has the module.exports = { app, add, multiply } line.
help me fix failing Jest tests in my Node.js project
- Create a new file called build.js by right-clicking in the Explorer sidebar and selecting New File.
- Paste the following code into build.js:
const fs = require('fs');
const path = require('path');
// Create a dist directory for the build output
const distDir = path.join(__dirname, 'dist');
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
// Copy the essential files into dist
const filesToCopy = ['app.js', 'package.json'];
filesToCopy.forEach(file => {
fs.copyFileSync(
path.join(__dirname, file),
path.join(distDir, file)
);
});
console.log('Build complete! Files copied to dist/');
What does build.js do?
This script simulates a build step. It creates a dist/ folder and copies your application files into it. In a real project, this is where you'd bundle, minify, or compile your code.
Your pipeline will run npm run build to produce this output, then publish it as a pipeline artifact.
- Save build.js.
- Verify the build script works by running:
npm run build
You should see Build complete! Files copied to dist/ in the terminal. A dist folder appears in your Explorer sidebar.
Seeing 'Cannot find module' or no output?
Check that build.js is saved at the root of node-pipeline-demo (not inside a subfolder). Verify your package.json has "build": "node build.js" in the scripts section.
help me fix my build script in my Node.js project
✔️ Awesome, I've got everything!
Great. Double check you've saved all four files before moving on.
ⓧ I'd like to double check the full code
Here's each file as it should look in your project:
{
"name": "node-pipeline-demo",
"version": "1.0.0",
"description": "A Node.js app for learning Azure DevOps CI/CD pipelines",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "jest",
"build": "node build.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "4.21.2"
},
"devDependencies": {
"jest": "29.7.0"
}
}
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from Azure DevOps Pipeline!', status: 'running' });
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { app, add, multiply };
if (require.main === module) {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
}
const { add, multiply } = require('./app');
describe('Math functions', () => {
test('add returns the sum of two numbers', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test('multiply returns the product of two numbers', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(-1, 5)).toBe(-5);
expect(multiply(0, 100)).toBe(0);
});
});
const fs = require('fs');
const path = require('path');
const distDir = path.join(__dirname, 'dist');
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir);
}
const filesToCopy = ['app.js', 'package.json'];
filesToCopy.forEach(file => {
fs.copyFileSync(
path.join(__dirname, file),
path.join(distDir, file)
);
});
console.log('Build complete! Files copied to dist/');
Push your code to Azure Repos
Your application is ready locally. Now you'll commit all the files and push them to Azure Repos so they're available for your pipeline to use.
- 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 origin main
What do these commands do?
- git add . stages every file in the folder for the next commit.
- git commit -m "..." saves a snapshot of your staged files with a message.
- git push origin main uploads your commit to the main branch on Azure Repos.
- Go back to your browser.
- In your Azure DevOps project, click Repos in the left sidebar.
- Click Files.
You should see app.js, app.test.js, build.js, and package.json listed in the repository.
Files not showing in Azure Repos?
Make sure git push completed without errors. If you see a rejection, try running git pull --rebase origin main first, then push again. Also verify you're looking at the main branch in the branch dropdown.
help me push code to Azure Repos
Your repository is live with a testable Node.js application. Next up, you'll set up a self-hosted build agent that can run your pipeline jobs.
Set Up a Self-Hosted Build Agent
Your code is now in Azure Repos, ready to be built and tested automatically.
For a pipeline to run, it needs somewhere to execute. That "somewhere" is called a build agent. In this step, you will set up a self-hosted agent on your own machine that connects to Azure DevOps and listens for pipeline jobs.
In this step, get ready to:
- Create a self-hosted agent pool in Azure DevOps.
- Download and configure the build agent on your Windows machine.
- Verify the agent is online and ready to pick up jobs.
Create an agent pool in Azure DevOps
An agent pool is a container that holds one or more agents. Your pipeline YAML will reference this pool name so Azure DevOps knows which agent should run the job.
- In your node-pipeline-demo project, click the gear icon (Project Settings) in the bottom-left corner of the sidebar.
- Under the Pipelines section in the left menu, click Agent pools.
- Click Add pool in the top-right area.
- For Pool type, select Self-hosted.
- Enter MyLocalPool as the name.
- Click Create.
You should see MyLocalPool listed under your agent pools. It has zero agents for now.
Why a self-hosted agent instead of Microsoft-hosted?
New Azure DevOps organizations do not automatically receive a free Microsoft-hosted parallel job. You must submit a request and wait several business days for approval.
A self-hosted agent lets you start immediately because every organization gets 1 free self-hosted parallel job with unlimited minutes. It also teaches you how agents work under the hood.
Download and configure the agent on your Windows machine
Before you download the agent software, you need a Personal Access Token (PAT) to authenticate the agent with Azure DevOps. The PAT is only used during the initial registration.
Create a Personal Access Token
- In Azure DevOps, click your user icon in the top-right corner.
- Select Personal access tokens.
- Click + New Token.
- Give the token a name like agent-setup.
- Set the Expiration to 30 days (or your preference).
- Click Show all scopes at the bottom of the dialog to see the full list.
- Under Agent Pools, check 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.
What is a Personal Access Token?
A PAT is an alternative password scoped to specific permissions. Here, you grant it only the Agent Pools (read, manage) scope so it can register an agent but nothing else.
The token is only used during the initial agent setup. After registration, the agent communicates with Azure DevOps using a different mechanism.
Download and extract the agent
- Go back to Project Settings > Agent pools > MyLocalPool.
- Click New agent in the top-right.
- Select the Windows tab and click the Download button to download the agent zip file.
- Create a folder at C:\agent and extract the zip contents into it.
Use a path without spaces
Microsoft recommends extracting the agent to a path without spaces (like C:\agent) to avoid script and permission issues.
Run the configuration
- 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
- Start the configuration wizard by running:
.\config.cmd
The wizard will ask you a series of questions. Enter the following at each prompt:
- Server URL: enter your organization URL in the format https://dev.azure.com/your-org-name (replace your-org-name with the organization name you created earlier).
- Authentication type: press Enter to accept the default (PAT).
- Personal access token: paste the token you copied earlier.
- Agent pool: enter MyLocalPool.
- Agent name: press Enter to accept the default (your machine name).
- Work folder: press Enter to accept the default (_work).
- Run agent as service: enter N (you will run it interactively for this project).
You should see a message confirming the agent was configured successfully.
Agent configuration failing?
If you see an authentication error, double-check that your PAT has the Agent Pools (read, manage) scope and has not expired.
If the agent cannot connect, verify your server URL matches the format https://dev.azure.com/your-org-name exactly. Do not include a trailing slash or project name.
Help me troubleshoot my agent configuration error.
Start the agent and verify it connects
With configuration complete, you can now start the agent so it begins listening for pipeline jobs from Azure DevOps.
- In the same PowerShell window (still in C:\agent), start the agent by running:
.\run.cmd
You should see output indicating the agent is listening for jobs. Keep this PowerShell window open.
- Go back to Azure DevOps in your browser.
- Navigate to Project Settings > Agent pools > MyLocalPool.
- Click the Agents tab.
- Confirm your agent appears with a green Online status.
What should I see?
Under the Agents tab, you should see one agent listed with your machine's name and a green "Online" indicator. This means the agent is connected and ready to receive pipeline jobs.
Agent not showing as Online?
Make sure the PowerShell window running .\run.cmd is still open. If you closed it, the agent stops.
Try refreshing the Azure DevOps page. It can take a few seconds for the status to update.
My self-hosted agent is not showing as Online in Azure DevOps.
Your agent is live and waiting for work. Next up, you will write the pipeline YAML that tells this agent exactly what to build, test, and publish.
Write Your CI/CD Pipeline YAML
Your self-hosted agent is online and waiting for instructions. The next step is to define exactly what work it should do when new code arrives.
In Azure DevOps, you define your entire build-and-test workflow in a YAML file stored alongside your code. This approach is called "pipeline as code" and it means your CI/CD configuration is version-controlled just like your application.
In this step, get ready to:
- Create the azure-pipelines.yml file with a Build stage that installs, tests, and builds your app.
- Add a Publish stage that packages your application as a downloadable artifact.
- Push the pipeline file to Azure Repos so it is ready to run.
Create the azure-pipelines.yml file
The pipeline file lives at the root of your repository. It tells Azure DevOps which branch to watch, which agent pool to use, and what steps to execute.
- In VS Code, right-click in the Explorer sidebar (the file panel on the left) and select New File.
- Name the file azure-pipelines.yml and press Enter.
- Paste the following code into the new file:
trigger:
branches:
include:
- main
pool:
name: MyLocalPool
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
displayName: 'Install, Build, and Test'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: 'Install Node.js'
- script: npm install
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run unit tests'
- script: npm run build
displayName: 'Build application'
What does this code do?
The trigger section tells Azure DevOps to run this pipeline automatically whenever code is pushed to the main branch.
The pool section points to your self-hosted agent pool MyLocalPool, so jobs run on your local machine.
- NodeTool@0 installs Node.js 20.x on the agent so your app can run.
- npm install downloads your project dependencies (Express, Jest).
- npm test runs your Jest unit tests to verify your code works.
- npm run build runs your build.js script to copy files into a dist/ folder.
Why YAML instead of the visual editor?
Azure DevOps offers a visual "Classic" editor for pipelines, but Microsoft recommends YAML for all new pipelines. YAML files live in your repository, so every pipeline change is tracked in version control.
If someone accidentally breaks the pipeline, you can revert to a previous commit. This is the industry-standard approach called "pipeline as code."
- Save azure-pipelines.yml by pressing Ctrl+S (Windows) or Cmd+S (macOS).
- Confirm the file appears in your VS Code Explorer sidebar at the same level as app.js and package.json.
File not appearing at the root?
Make sure you created the file at the top level of your node-pipeline-demo folder, not inside a subfolder. It should sit alongside app.js, not inside node_modules or any other directory.
Also check the file extension is .yml, not .yaml or .txt. help me fix my azure-pipelines.yml file location
Add the Publish Artifacts stage
The Build stage validates your code works. Now you need a second stage that packages the build output into a downloadable artifact.
- In azure-pipelines.yml, place your cursor at the end of the file (after the last line of the Build stage).
- Add the following Publish stage below the Build stage:
- stage: Publish
displayName: 'Publish Artifacts'
dependsOn: Build
jobs:
- job: PublishJob
displayName: 'Publish build output'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: 'Install Node.js'
- script: npm install
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: 'dist'
artifactName: 'node-app'
displayName: 'Publish build artifact'
What does this stage do?
dependsOn: Build means this stage only runs after the Build stage passes. If your tests fail, the Publish stage never executes.
The PublishBuildArtifacts@1 task uploads your dist/ folder to Azure DevOps as an artifact named node-app. Anyone on your team can then download this packaged build.
The Publish stage reinstalls Node.js and dependencies because each stage runs on a clean workspace. Nothing carries over between stages automatically.
- Save azure-pipelines.yml.
- Check that the - stage: Publish line is indented at the same level as - stage: Build above it (two spaces from the left edge).
Seeing red squiggly lines in the YAML?
YAML is very sensitive to indentation. Every level uses exactly 2 spaces (not tabs). If VS Code highlights errors, check that your indentation matches the code above exactly.
The Azure Pipelines extension you installed in Step 1 provides syntax validation. Hover over any red underline to see the specific error. help me fix YAML indentation errors in my pipeline file
✔️ Awesome, I've got everything!
Great. Double check you have saved azure-pipelines.yml before moving on to the next substep.
ⓧ I'd like to double check the full code
Here is the complete azure-pipelines.yml file with both stages:
trigger:
branches:
include:
- main
pool:
name: MyLocalPool
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
displayName: 'Install, Build, and Test'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: 'Install Node.js'
- script: npm install
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run unit tests'
- script: npm run build
displayName: 'Build application'
- stage: Publish
displayName: 'Publish Artifacts'
dependsOn: Build
jobs:
- job: PublishJob
displayName: 'Publish build output'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: 'Install Node.js'
- script: npm install
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: 'dist'
artifactName: 'node-app'
displayName: 'Publish build artifact'
Commit and push the pipeline file
Your pipeline YAML is complete, but it only exists on your local machine. Azure DevOps needs it in the repository to detect and use it.
- Open your terminal in VS Code by pressing Ctrl+` (Windows) or Cmd+` (macOS).
- Stage and commit the pipeline file by running these commands:
git add azure-pipelines.yml
git commit -m "Add CI/CD pipeline configuration"
- Push the commit to Azure Repos by running:
git push origin main
- In your browser, go to your node-pipeline-demo project in Azure DevOps.
- Click Repos in the left sidebar.
- Verify that azure-pipelines.yml appears in the file list alongside app.js, app.test.js, and package.json.
Push rejected or authentication error?
If Git asks for credentials, use the same username and Personal Access Token you used earlier. Make sure you are on the main branch by running git branch to check.
If you see "everything up-to-date" it means there is nothing new to push. Run git status to confirm there are no uncommitted changes. help me push my pipeline file to Azure Repos
Your pipeline definition is now live in the repository. Next up, you will create the pipeline in Azure DevOps and watch it run both stages on your self-hosted agent.
Create and Run Your Pipeline
Your pipeline YAML is committed to the repository and your self-hosted agent is online and ready to pick up jobs.
Now it is time to connect the pieces together. In this step, you will create a pipeline in Azure DevOps that points to your azure-pipelines.yml file, run it manually, and then watch it trigger automatically when you push a code change.
In this step, get ready to:
- Create a pipeline in Azure DevOps pointing to your YAML file.
- Run the pipeline and verify both stages pass.
- Trigger an automatic pipeline run with a code push.
Create the pipeline in Azure DevOps
Azure DevOps needs to know which YAML file defines your pipeline. You will point it to the azure-pipelines.yml file already sitting in your repository.
- In your Azure DevOps project, click Pipelines in 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 Azure Repos Git as your code source.
- Select your node-pipeline-demo repository.
- On the "Configure your pipeline" screen, choose Existing Azure Pipelines YAML file.
- In the dialog that appears, make sure the branch is set to main and the path is set to /azure-pipelines.yml.
- Click Continue.
Azure DevOps now shows a preview of your YAML file. You will run it in the next substep.
What just happened?
You told Azure DevOps where to find your pipeline definition. From now on, every time this pipeline runs, Azure DevOps reads azure-pipelines.yml from your repository to decide what stages and steps to execute.
This is the "pipeline as code" approach. Your entire build workflow lives in version control alongside your application code.
Run the pipeline and watch it execute
With the pipeline created, you can trigger it manually to confirm everything works before relying on automatic triggers.
- On the YAML preview screen, click Run in the top right.
- Azure DevOps queues the pipeline run and sends it to your self-hosted agent in MyLocalPool.
What should I see?
The pipeline run page shows two stages: Build and Test and Publish Artifacts. Each stage lights up green as it completes.
Inside the Build stage, you will see individual steps for installing Node.js, installing dependencies, running unit tests, and building the application. The Publish stage installs dependencies, builds again, then publishes the dist folder as an artifact named node-app.
- Click into the Build and Test stage to expand the job log. Confirm that the "Run unit tests" step shows all tests passing.
- Click back to the pipeline run summary and confirm that the Publish Artifacts stage also shows a green checkmark.
- On the run summary page, look for the Artifacts section. You should see an artifact called node-app listed there.
Pipeline stuck on "Queued"?
If the run stays in a queued state and never starts, your agent is not running or not connected to the correct pool.
- Go to Project Settings > Agent pools > MyLocalPool and confirm the agent shows as Online.
- If it shows Offline, open PowerShell at C:\agent and run .\run.cmd again.
- Make sure the pool name in your azure-pipelines.yml matches exactly: name: MyLocalPool.
Still stuck? Why is my Azure DevOps pipeline stuck on queued and not running on my self-hosted agent?
Trigger the pipeline automatically with a code change
Your azure-pipelines.yml includes a trigger on the main branch. This means any push to main automatically kicks off a new pipeline run.
What is a CI trigger?
A CI trigger tells Azure DevOps to start a pipeline run every time code is pushed to a specified branch. This is the "Continuous Integration" part of CI/CD. Developers push code, and the system automatically validates it.
- Open app.js in VS Code.
- Add a comment at the very top of the file, such as // Trigger pipeline test.
- Save the file.
- Stage, commit, and push the change by running these commands:
git add .
git commit -m "Test automatic pipeline trigger"
git push origin main
- Go back to your Azure DevOps project in the browser.
- Click Pipelines in the left navigation.
- You should see a new run has been triggered automatically. Click into it to watch it execute.
- Confirm both the Build and Test and Publish Artifacts stages pass with green checkmarks.
What does this prove?
You just experienced the core value of Continuous Integration. You pushed code and the pipeline ran automatically without any manual clicks. Every future commit to main will go through the same automated build-and-test cycle.
No automatic run appeared?
- Make sure you pushed to the main branch specifically. Other branches will not trigger this pipeline.
- Confirm your azure-pipelines.yml trigger section includes main under branches > include.
- Check that the agent is still running at C:\agent. If the PowerShell window was closed, the agent went offline.
Need help? My Azure DevOps pipeline is not triggering automatically when I push to main. How can I debug CI triggers?
Your CI/CD pipeline is now fully operational. Every push to main triggers an automated build, test, and artifact publish cycle. Next up, take on the Secret Mission and add a deployment stage with approval gates.
Secret mission
Add a Deployment Stage with Approvals
Your pipeline builds and publishes artifacts automatically. But in a real team, nobody ships code to production without a human saying "go". Can you add a Deploy stage that pauses for approval before copying your build to a deployment folder?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on the Azure DevOps free tier with a self-hosted agent, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Azure DevOps organization and project (node-pipeline-demo).
- Self-hosted build agent (C:\agent folder and running process).
- Personal Access Token (PAT) for agent authentication.
- Local Git repository clone on your machine.
- Node.js, Git, and VS Code installed on your machine.
Keep everything running
No action needed. Choose this if you are still actively building or plan to experiment with more pipeline configurations.
- Your self-hosted agent continues to run in its PowerShell window. Close the window any time to stop it.
- Your Azure DevOps organization stays on the free tier with no charges.
- Node.js, Git, and VS Code remain installed on your machine.
Pause - I'll come back to this later
Shut down running processes to free up resources while keeping your files and project intact for later.
- Stop your self-hosted agent by pressing Ctrl+C in the PowerShell window where it is running.
- Your Azure DevOps project and repository remain available in the browser whenever you return.
- To restart the agent later, open PowerShell, navigate to C:\agent, and run .\run.cmd again.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
- Stop the self-hosted agent by pressing Ctrl+C in its PowerShell window.
- Delete the agent folder by running this in PowerShell:
Remove-Item -Recurse -Force C:\agent
- Revoke your Personal Access Token. In Azure DevOps, click your profile icon in the top right, select User settings, then Personal access tokens. Select the token you created and click Revoke. Confirm by clicking Revoke in the dialog.
- Delete the Azure DevOps project. Go to Organization settings (bottom-left gear icon), select Projects, check node-pipeline-demo, and click Delete. Enter the project name to confirm.
- Delete the Azure DevOps organization. Go to Organization settings, select Overview, and click Delete. Enter the organization name to confirm. You can recover the organization within 28 days if you change your mind.
- Delete your local repository folder. Open PowerShell and run:
Remove-Item -Recurse -Force ~\Desktop\node-pipeline-demo
- Uninstall Node.js. Open Settings, select Apps, then Installed apps. Search for Node.js and click Uninstall.
- Uninstall Git. In the same Installed apps list, search for Git and click Uninstall.
- Uninstall VS Code. In the same Installed apps list, search for Visual Studio Code and click Uninstall.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete CI/CD pipeline from scratch using Azure DevOps.
Every time you push code to your repository, your pipeline automatically builds, tests, and packages your Node.js application. That's production-grade automation.
You've learned how to:
- Configure a self-hosted build agent on your Windows machine that executes pipeline jobs.
- Write a multi-stage YAML pipeline with Build, Test, and Publish stages that run sequentially.
- Set up automatic triggers that fire the pipeline on every commit to the main branch.
- Added a deployment stage with environment approvals, simulating a real production release workflow.
Ready to quiz yourself?