Set Up a CI/CD Pipeline with Azure DevOps
Build a YAML pipeline that auto-builds and tests a Node.js app on every push.
Introduction
30 Second Summary
Every time you change code, someone needs to make sure it still works. Most professional teams automate this process so bugs get caught before they ever reach users.
In this project, you will build an automated pipeline using Azure DevOps that builds and tests a Node.js application every time you push code to GitHub.
What You'll Build
A fully automated CI/CD pipeline in Azure DevOps that watches your GitHub repository and automatically installs dependencies, runs your test suite, and publishes a build artifact on every push to main.
By the end of this project, you'll have:
- A GitHub repository with a simple Node.js app and automated test suite.
- An Azure DevOps project connected to your GitHub repo with a multi-stage YAML pipeline.
- A live pipeline that automatically triggers on every push, proving your CI workflow catches issues early.
- Secret Mission: Add a deployment stage that pushes your app live to Azure App Service.
Are there any prerequisites?
You'll need basic familiarity with Git (commit, push, pull). No prior CI/CD experience is required.
Set Up Your Accounts and Tools
Before you can build a pipeline, you need the tools and accounts that make it all work. Git tracks your code changes. Node.js runs your application. GitHub hosts your repository. And Azure DevOps orchestrates the pipeline itself.
In this step, you will install Git and Node.js, create your GitHub and Azure DevOps accounts, and request the free parallelism that lets your pipeline run.
In this step, get ready to:
- Install Git and Node.js on your machine and verify both work.
- Create a free GitHub account and a free Azure DevOps organization.
- Request free pipeline parallelism for your Azure DevOps organization.
Install Git and Node.js
Git is the version control tool that tracks every change to your code. Node.js is the JavaScript runtime that runs your application and its test suite.
- 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 if Git is already installed by running this command:
git --version
✔️ I see a version number
Git is ready to go. Move on to the Node.js check below.
ⓧ Command not found
You need to install Git. Follow the instructions for your operating system.
macOS
- Run git in your terminal. macOS will prompt you to install Xcode Command Line Tools.
- Click Install when the dialog appears and wait for it to finish.
- Once complete, run git --version again to confirm it works.
Windows
- Open https://git-scm.com/download/win in your browser. The download starts automatically.
- Run the installer and accept all default options.
- Close and reopen PowerShell, then run git --version to confirm it works.
Now check if Node.js is installed by running this command:
node --version
✔️ I see version 20 or higher
Node.js is ready. You can move on to the next substep.
ⓧ I see an older version
Your pipeline uses Node.js 20.x so you need version 20 or higher installed locally for compatibility.
- Go to https://nodejs.org/en/download in your browser.
- Download and run the LTS installer for your operating system.
- The installer will replace your current version.
- Close and reopen your terminal, then run node --version again to confirm the version is 20 or higher.
ⓧ Command not found
You need to install Node.js.
- Go to https://nodejs.org/en/download in your browser.
- Download and run the LTS installer for your operating system.
- Follow the installer prompts and accept all defaults.
- Close and reopen your terminal, then run node --version to confirm you see version 20 or higher.
Still seeing command not found?
Make sure you closed and reopened your terminal after installing. New installations update your system PATH, but existing terminal sessions do not pick up the change automatically.
help me troubleshoot why Git or Node.js is not found after installing
Create your GitHub and Azure DevOps accounts
GitHub stores your code in a remote repository. Azure DevOps connects to that repository and runs your pipeline every time you push changes.
- Open https://github.com in your browser.
- If you already have an account, sign in. Otherwise, click Sign up and follow the prompts to create a free account.
Now create your Azure DevOps organization.
- Open https://dev.azure.com in your browser.
- Sign in with a Microsoft account (or create one for free).
- When prompted, create a new organization. Give it any name you like.
- You will land on your organization's home page at https://dev.azure.com/your-organization.
No credit card required
Azure DevOps does not require a credit card to get started. The free tier includes 1 Microsoft-hosted CI/CD job with 1,800 minutes per month, which is more than enough for this project.
Request free parallelism
New Azure DevOps organizations no longer receive free Microsoft-hosted parallelism by default. You need to submit a short request form before your pipelines can run on Microsoft-hosted agents.
- Open https://aka.ms/azpipelines-parallelism-request in your browser.
- Fill in the form with your Azure DevOps organization name and email address.
- Submit the request.
How long does approval take?
The request takes 4-5 business days to process. You can continue setting up everything else in this project while you wait. Your pipeline will run once the request is approved.
If you want to test your pipeline before approval arrives, you could set up a self-hosted agent on your own machine. However, this project uses the standard Microsoft-hosted path that most organizations follow.
Not sure what to enter in the form?
For the organization name, use the name you chose when you created your Azure DevOps organization. You can find it in the URL: dev.azure.com/your-organization-name.
help me fill in the Azure DevOps parallelism request form
Your tools are installed and your accounts are ready. Next up, you will create a Node.js application and push it to GitHub so your pipeline has something to build.
Create Your Node.js App and Push to GitHub
Your accounts and tools are set up. You have Git, Node.js, a GitHub account, and an Azure DevOps organization ready to go.
A pipeline needs something to build and test. In this step, you'll create a simple Express web server with automated tests, then push it to GitHub so Azure DevOps can access it later.
In this step, get ready to:
- Create a GitHub repository and clone it to your machine.
- Build a Node.js Express app with a test suite.
- Push all your code to the main branch on GitHub.
Create your GitHub repository and initialize the project
Your pipeline will pull code from a GitHub repository. You need to create that repository first, then set up the Node.js project inside it.
- Open your browser and go to github.com.
- In the upper-right corner of the page, click the + dropdown menu.
- Click New repository.
- Type azure-pipeline-demo in the Repository name field.
- Leave the visibility set to Public.
- Do NOT check Add a README file. You'll push your own files from the command line.
- Click Create repository.
Why skip the README?
Creating a completely empty repository lets you push your local project directly without merge conflicts. If GitHub adds a README, your local project and the remote would have different initial commits.
GitHub shows you a Quick setup page with your repository URL. Keep this page open.
- Open your terminal by pressing the Windows key, typing Terminal, and pressing Enter.
- Move to your Desktop and clone the repository by running these commands:
cd ~/Desktop
git clone [[REPO_URL="https://github.com/your-username/azure-pipeline-demo.git"]]
cd azure-pipeline-demo
Where do I find my repository URL?
On the Quick setup page GitHub showed you after creating the repository, copy the HTTPS URL. It looks like https://github.com/your-username/azure-pipeline-demo.git.
- Initialize a Node.js project by running this command:
npm init -y
What does npm init -y do?
This creates a package.json file with default values. The -y flag skips all the interactive prompts and accepts the defaults automatically.
- Confirm the file was created by running:
ls
You should see package.json listed in the output.
- Press Cmd+Space (macOS) or the Windows key (Windows) 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 your Desktop, select the azure-pipeline-demo folder, and click Select Folder.
Now you'll create the Express server file.
- In the VS Code Explorer sidebar on the left, click the New File icon (page with a plus sign).
- Name the file index.js and press Enter.
- Paste the following code into index.js:
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from Azure Pipeline Demo!', status: 'running' });
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
if (require.main === module) {
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}
module.exports = app;
What does this code do?
This creates a minimal Express web server with two routes. The / route returns a JSON greeting, and /health returns the server's health status with a timestamp.
The if (require.main === module) block only starts the server when run directly. This lets your test file import the app without starting the server.
- Save index.js with Ctrl+S.
Next, create the test file.
- In the Explorer sidebar, click the New Folder icon.
- Name the folder test and press Enter.
- Click on the test folder to select it, then click the New File icon.
- Name the file app.test.js and press Enter.
- Paste the following code into test/app.test.js:
const app = require('../index');
describe('App', () => {
test('should export the express app', () => {
expect(app).toBeDefined();
});
test('should have GET / route', async () => {
const request = require('supertest');
const response = await request(app).get('/');
expect(response.status).toBe(200);
expect(response.body.message).toBe('Hello from Azure Pipeline Demo!');
expect(response.body.status).toBe('running');
});
test('should have GET /health route', async () => {
const request = require('supertest');
const response = await request(app).get('/health');
expect(response.status).toBe(200);
expect(response.body.status).toBe('healthy');
expect(response.body).toHaveProperty('timestamp');
});
});
What do these tests check?
The test suite uses Jest to verify your app works correctly. The first test confirms the app exports properly. The second and third tests make HTTP requests to your routes and check the responses match expected values.
These tests run automatically in your pipeline later, catching bugs before they reach production.
- Save test/app.test.js with Ctrl+S.
Don't see the test folder in the sidebar?
Make sure you opened the azure-pipeline-demo folder (not your Desktop) in VS Code. If you see too many files, click File then Open Folder and select the correct project folder.
Help me find my test folder in VS Code
Configure your package.json and install dependencies
The package.json file created by npm init is bare-bones. You need to add scripts that your pipeline will run, plus declare the dependencies your app needs.
- In VS Code, click on package.json in the Explorer sidebar to open it.
- Select all the content with Ctrl+A and delete it.
- Paste the following complete package.json content:
{
"name": "azure-pipeline-demo",
"version": "1.0.0",
"description": "A simple Node.js app to demonstrate Azure DevOps CI/CD pipelines",
"main": "index.js",
"scripts": {
"start": "node index.js",
"build": "node -e \"const fs=require('fs'); fs.mkdirSync('dist',{recursive:true}); fs.copyFileSync('index.js','dist/index.js'); fs.copyFileSync('package.json','dist/package.json'); console.log('Build complete');\"",
"test": "jest --ci --reporters=default --reporters=jest-junit"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"express": "4.21.2"
},
"devDependencies": {
"jest": "29.7.0",
"jest-junit": "16.0.0"
},
"jest-junit": {
"outputDirectory": "./test-results",
"outputName": "junit.xml"
}
}
What do these scripts do?
- start runs your server directly with Node.js.
- build copies your app files into a dist folder, simulating a build step that your pipeline will use to produce an artifact.
- test runs Jest with the --ci flag (optimized for continuous integration) and outputs results in JUnit XML format so Azure DevOps can display them.
The jest-junit config at the bottom tells the test reporter where to save the XML results file.
- Save package.json with Ctrl+S.
- Open the VS Code integrated terminal by pressing Ctrl+` (backtick key, usually above Tab).
- Install all the dependencies listed in package.json by running:
npm install
- Also install the supertest package (used by your test file to make HTTP requests) by running:
npm install --save-dev supertest
You should see output confirming packages were added. A node_modules folder and package-lock.json file now appear in your Explorer sidebar.
Seeing permission errors during install?
Make sure your terminal is inside the azure-pipeline-demo folder. Run pwd to check your current directory. If you're in the wrong place, run cd ~/Desktop/azure-pipeline-demo to navigate there.
Help me fix npm install errors
✔️ Awesome, I've got everything!
Great. Double check that you've saved all three files before moving on.
ⓧ I'd like to double check the full code
Here is the complete content for each file at this point:
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from Azure Pipeline Demo!', status: 'running' });
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
if (require.main === module) {
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}
module.exports = app;
const app = require('../index');
describe('App', () => {
test('should export the express app', () => {
expect(app).toBeDefined();
});
test('should have GET / route', async () => {
const request = require('supertest');
const response = await request(app).get('/');
expect(response.status).toBe(200);
expect(response.body.message).toBe('Hello from Azure Pipeline Demo!');
expect(response.body.status).toBe('running');
});
test('should have GET /health route', async () => {
const request = require('supertest');
const response = await request(app).get('/health');
expect(response.status).toBe(200);
expect(response.body.status).toBe('healthy');
expect(response.body).toHaveProperty('timestamp');
});
});
{
"name": "azure-pipeline-demo",
"version": "1.0.0",
"description": "A simple Node.js app to demonstrate Azure DevOps CI/CD pipelines",
"main": "index.js",
"scripts": {
"start": "node index.js",
"build": "node -e \"const fs=require('fs'); fs.mkdirSync('dist',{recursive:true}); fs.copyFileSync('index.js','dist/index.js'); fs.copyFileSync('package.json','dist/package.json'); console.log('Build complete');\"",
"test": "jest --ci --reporters=default --reporters=jest-junit"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"express": "4.21.2"
},
"devDependencies": {
"jest": "29.7.0",
"jest-junit": "16.0.0"
},
"jest-junit": {
"outputDirectory": "./test-results",
"outputName": "junit.xml"
}
}
Commit and push your code to GitHub
Your app is ready locally. Now push it to GitHub so Azure DevOps can access it when you create your pipeline.
- In your VS Code terminal, commit and push all files by running these commands:
git add .
git commit -m "Initial commit with Express app and tests"
git push -u origin main
What do these commands do?
- git add . stages all files in the current folder for the next commit.
- git commit -m saves the staged changes as a snapshot with a descriptive message.
- git push -u origin main uploads your commit to the main branch on GitHub. The -u flag sets origin main as the default push target for future pushes.
- Go back to your GitHub repository page in the browser and refresh it.
You should see your files listed: index.js, package.json, package-lock.json, and the test folder.
Push rejected or authentication failed?
If Git asks for credentials, GitHub requires a Personal Access Token (not your password) for HTTPS authentication. In GitHub, open Settings, then Developer settings, then Personal access tokens, then Tokens (classic) to generate one with repo scope.
If you see an error about the branch not existing, run git branch -M main before the push command to rename your branch to main.
Help me push my code to GitHub
Your app is live on GitHub and ready for automation. Next, you'll connect this repository to Azure DevOps and create your first pipeline.
Create Your Azure DevOps Project and Connect GitHub
Your Node.js app is on GitHub, ready to be built and tested automatically. Now you need to tell Azure DevOps where your code lives so it can watch for changes and run your pipeline.
In this step, you will create a project inside your Azure DevOps organization and connect it to your GitHub repository. Once connected, Azure DevOps can access your code and generate a starter pipeline file.
In this step, get ready to:
- Create a new Azure DevOps project called Pipeline Demo.
- Connect Azure DevOps Pipelines to your GitHub repository.
- Generate a starter pipeline YAML file in your repo.
Create a new Azure DevOps project
A project in Azure DevOps is the container that holds your pipelines, boards, and repositories. You will create one dedicated project for this pipeline demo.
- Open your browser and navigate to your Azure DevOps organization at dev.azure.com.
- Click New project in the top-right corner of the Projects page.
- Enter Pipeline Demo as the project name.
- Leave Visibility set to Private.
- Under Advanced, confirm Version control is set to Git.
- Click Create.
Azure DevOps displays your new project's welcome page after a few seconds.
What is an Azure DevOps project?
A project is a container that isolates your pipelines, code repositories, boards, and test plans from other work in the same organization. Think of it like a folder that keeps everything for one application grouped together.
Don't see the New project button?
You need to be the organization owner or have the "Create new projects" permission. If you just created your Azure DevOps organization in the previous step, you already have this permission.
help me troubleshoot why I can't see the New project button in Azure DevOps
Connect your pipeline to GitHub
With your project created, you can now set up a pipeline that pulls code from your GitHub repository. Azure DevOps will ask you to authorize access to your GitHub account during this process.
- In your new Pipeline Demo project, click Pipelines in the left sidebar.
- Click Create Pipeline (or New pipeline if you see that option instead).
- On the "Where is your code?" screen, select GitHub as your code source.
- Azure DevOps will redirect you to GitHub to authorize access. Click Authorize AzurePipelines on the GitHub authorization page.
- After authorization, you will see a list of your GitHub repositories. Select your azure-pipeline-demo repository.
What is happening during authorization?
Azure DevOps installs the Azure Pipelines GitHub App on your GitHub account. This creates a secure connection (called a service connection) that lets Azure DevOps read your code and report build status back to GitHub.
You only need to authorize once. Future pipelines in this organization can reuse the same connection.
GitHub authorization not working?
Make sure you are signed in to the correct GitHub account in your browser. If you belong to a GitHub organization, you may need to grant access to that organization as well during the authorization step.
help me fix the GitHub authorization step when connecting Azure DevOps Pipelines to my GitHub repository
Select the starter pipeline template
After selecting your repository, Azure DevOps asks you to pick a pipeline template. The starter template gives you a minimal YAML file that you will replace with your own multi-stage pipeline in the next step.
- On the "Configure your pipeline" screen, select Starter pipeline.
- Azure DevOps opens a YAML editor showing a basic azure-pipelines.yml file. You do not need to edit this yet.
- Click Save and run in the top-right corner.
- In the dialog that appears, leave the commit message as-is and click Save and run again.
What does Save and run do?
This commits the azure-pipelines.yml file directly to the main branch of your GitHub repository. It also triggers the first pipeline run using the starter template. You will replace this file's contents in the next step.
You should see Azure DevOps kick off a pipeline run. The run may fail if your free parallelism request has not been approved yet. That is completely fine for now.
Seeing a message about parallelism?
If Azure DevOps shows a message that no hosted parallelism has been granted, your free parallelism request from the earlier step is still being processed. The pipeline is set up correctly. It will run once your request is approved.
help me understand the Azure DevOps parallelism message and when my pipeline will be able to run
Your Azure DevOps project is connected to GitHub and ready to go. Next up, you will replace the starter YAML with a proper multi-stage pipeline that builds and tests your Node.js app.
Write Your Multi-Stage YAML Pipeline
Your Azure DevOps project is connected to GitHub, and a starter pipeline file is waiting in your repo. Now it's time to write the actual pipeline logic that builds and tests your app automatically.
A YAML pipeline defines your entire automation workflow as code. You'll define stages, jobs, and steps that tell Azure DevOps exactly what to do every time you push code.
In this step, get ready to:
- Replace the starter YAML with a Build stage that installs dependencies and builds your app.
- Add a Test stage that runs your test suite and publishes results.
- Publish a build artifact and push your pipeline to GitHub.
Replace the starter YAML with your Build stage
Azure DevOps committed a starter azure-pipelines.yml to your GitHub repo when you saved the pipeline. Your local clone doesn't have this file yet.
- Pull the latest changes from GitHub by running this command in your terminal (inside the azure-pipeline-demo folder):
git pull
You should see output mentioning azure-pipelines.yml as a new file.
Seeing a merge conflict or error?
Make sure you committed and pushed all your files in Step 2 before pulling. If you see Already up to date, the file may already be local. Check with ls to confirm azure-pipelines.yml exists.
help me fix a git pull issue in my Azure DevOps pipeline project
Now you'll replace the starter content with your Build stage configuration.
- Open azure-pipelines.yml in VS Code.
- Select all the existing content and replace it with the following code:
trigger:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
nodeVersion: '20.x'
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Build Application'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: npm ci
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
What does this code do?
- trigger tells Azure DevOps to run this pipeline every time code is pushed to the main branch.
- pool sets the virtual machine image to ubuntu-latest. This is the cloud server that runs your pipeline steps.
- variables defines reusable values. Here, nodeVersion stores the Node.js version so you only need to change it in one place.
- The Build stage uses NodeTool@0 to install Node.js, npm ci for a clean install of dependencies, and npm run build to produce your app's build output.
- Save azure-pipelines.yml.
You won't see a visible change yet. The next chunks will complete the pipeline before you push it.
Seeing red squiggly lines in VS Code?
YAML is sensitive to indentation. Make sure you're using spaces (not tabs) and that each level is indented by exactly 2 spaces. If you pasted the code and see errors, check that no extra whitespace crept in at the start of lines.
help me fix YAML indentation errors in my azure-pipelines.yml file
Add the Test stage
A CI/CD pipeline isn't complete without automated tests. Your Test stage will run your Jest test suite and publish the results so Azure DevOps can display them.
- In azure-pipelines.yml, place your cursor at the end of the file (after the last line of the Build stage).
- Add the following Test stage below the Build stage:
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: TestJob
displayName: 'Run Tests'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: npm ci
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run tests'
- task: PublishTestResults@2
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results/junit.xml'
condition: succeededOrFailed()
What does this code do?
- dependsOn: Build ensures the Test stage only runs after the Build stage passes. If the build fails, tests are skipped.
- npm test runs your Jest test suite (configured in package.json).
- PublishTestResults@2 uploads your test results in JUnit format so Azure DevOps displays them in a visual dashboard.
- condition: succeededOrFailed() means test results are published even if some tests fail. This gives you visibility into what went wrong.
- Save azure-pipelines.yml.
You won't see a change yet. The next chunk adds the final piece to your Build stage.
Not sure if the indentation is correct?
The - stage: Test line should be at the same indentation level as - stage: Build above it (2 spaces in from stages:). Both stages are items in the stages list.
help me check the indentation of my Test stage in azure-pipelines.yml
Publish a build artifact and push your changes
A build artifact is a packaged version of your app that later stages (like deployment) can use. You'll add a task that saves your dist folder as a downloadable artifact in Azure DevOps.
- In azure-pipelines.yml, find the last step in your Build stage. It looks like this:
- script: npm run build
displayName: 'Build application'
- Add the following task directly below it (at the same indentation level as the other steps):
- task: PublishBuildArtifacts@1
displayName: 'Publish build artifact'
inputs:
pathToPublish: 'dist'
artifactName: 'app-build'
What does this code do?
- PublishBuildArtifacts@1 takes a folder from your build and stores it in Azure DevOps as a downloadable artifact.
- pathToPublish: 'dist' points to the folder created by npm run build (your compiled app files).
- artifactName: 'app-build' gives the artifact a name you can reference in later stages like deployment.
- Save azure-pipelines.yml.
Your pipeline file is complete. Time to commit and push it to GitHub so Azure DevOps can pick it up.
- Commit and push your updated pipeline file by running these commands:
git add azure-pipelines.yml
git commit -m "Add multi-stage Build and Test pipeline"
git push
- You should see the push succeed with a message showing objects being written to GitHub.
Push rejected or authentication error?
If you see a permission error, make sure you're authenticated with GitHub. Try running git push again and enter your credentials or personal access token when prompted.
If the push is rejected because the remote has changes you don't have locally, run git pull --rebase first, then try git push again.
help me fix a git push error in my Azure DevOps pipeline project
✔️ Awesome, I've got everything!
Great. Double check you've saved azure-pipelines.yml and that your push to GitHub succeeded.
ⓧ I'd like to double check the full code
Here's the complete azure-pipelines.yml file. Make sure yours matches exactly:
trigger:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
nodeVersion: '20.x'
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Build Application'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: npm ci
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
- task: PublishBuildArtifacts@1
displayName: 'Publish build artifact'
inputs:
pathToPublish: 'dist'
artifactName: 'app-build'
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: TestJob
displayName: 'Run Tests'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: npm ci
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run tests'
- task: PublishTestResults@2
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results/junit.xml'
condition: succeededOrFailed()
Your multi-stage pipeline is now live on GitHub. Next up, you'll watch Azure DevOps pick it up and run it automatically.
Run and Monitor Your Pipeline
Your pipeline is fully configured and connected to your GitHub repository. Now it is time for the moment of truth: watching Azure DevOps execute your pipeline automatically.
In this step, you will monitor your first pipeline run in real-time, verify that both stages pass, and push a code change to prove your CI trigger works.
In this step, get ready to:
- Watch your first pipeline run execute in real-time.
- Verify both stages pass and check the published build artifact.
- Trigger a second run with a code change to confirm CI automation.
Watch your first pipeline run
When you saved your pipeline in the previous step, Azure DevOps triggered the first run automatically. You should already see a run in progress or recently completed.
Pipeline stuck in queue?
If your pipeline shows as queued but never starts running, your free parallelism request may not yet be approved. New Azure DevOps organizations require a manual review that takes 4-5 business days.
Navigate to Organization settings, then Pipelines, then Parallel jobs to check if Microsoft-hosted shows a number greater than 0. If it shows 0, your request is still pending.
Help me troubleshoot my Azure DevOps pipeline that is stuck in queue and not starting.
- In your Azure DevOps project, click Pipelines in the left sidebar.
- Click on the run (it will show as in-progress or completed) to open the run detail view.
You will see the two stages of your pipeline: Build Stage and Test Stage.
- Click on the Build Stage box to expand its job details.
- Click on Build Application to see the step-by-step logs.
Each step executes in order: Install Node.js, Install dependencies, Build application, and Publish build artifact.
What am I looking at?
The log view shows real-time output from the Microsoft-hosted agent running your code. Each step appears with a green checkmark when it succeeds.
This is the same output you would see running npm ci and npm run build on your local machine. The difference is it happens on a fresh cloud VM every time.
Verify stages and check the published artifact
Once both stages finish, you need to confirm everything passed and that your build artifact was published successfully.
- Navigate back to the run summary by clicking the back arrow or the run number at the top.
- Confirm that both Build Stage and Test Stage show green checkmarks.
- Look for the 1 artifact produced link in the run summary section.
- Click on 1 artifact produced to see your published artifact named app-build.
What is a build artifact?
A build artifact is the packaged output of your build process. In this case, it is the dist folder containing your application files ready for deployment.
Artifacts persist after the pipeline run finishes, so other stages (like a deployment stage) can pick them up and use them.
Seeing a red X on a stage?
If the Build stage failed, expand the failed step's logs and look for the error message. A common issue is a typo in azure-pipelines.yml causing YAML parsing errors.
If the Test stage failed, check that supertest is listed in your package.json dependencies. Missing test dependencies are the most common cause of test failures in pipelines.
Help me fix my failing Azure DevOps pipeline stage.
Trigger a second run with a code change
A CI pipeline is only useful if it runs automatically when you push code. The real proof is triggering a second run with a fresh commit.
- Return to your terminal and make sure you are inside the azure-pipeline-demo folder.
- Open index.js in your editor and find this line:
res.json({ message: 'Hello from Azure Pipeline Demo!', status: 'running' });
- Change the message string to something different, for example Hello from Azure Pipeline Demo v2!.
- Save index.js.
- Commit and push the change by running these commands:
git add .
git commit -m "Update welcome message"
git push origin main
What does this prove?
Your azure-pipelines.yml has a trigger set to run on pushes to main. By pushing a commit, you prove the pipeline detects the change and runs automatically without any manual intervention.
- Switch back to Azure DevOps in your browser.
- Click Pipelines in the left sidebar.
You should see a new run appear within a few seconds, triggered by your push.
- Wait for it to complete and confirm both stages pass with green checkmarks again.
Don't see a new run?
Make sure your git push completed successfully in the terminal. Check for authentication errors or network issues.
Verify that you pushed to the main branch specifically. Pushes to other branches will not trigger this pipeline because the trigger is configured for main only.
Help me troubleshoot why my Azure DevOps pipeline did not trigger after I pushed to GitHub.
You now have a fully working CI pipeline. Every push to main automatically builds your app, runs your tests, and publishes an artifact. Next up, you will take on a Secret Mission to add a deployment stage that pushes your app live.
Secret mission
Add a Deployment Stage
Your pipeline builds and tests automatically. But what about deploying? In this secret mission, you'll extend your pipeline with a third stage that deploys your Node.js app to a live Azure App Service URL, complete with a manual approval gate so no code hits production without a human sign-off.
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. Both Azure DevOps and GitHub are within their free tiers, so there are no ongoing costs from this project.
Resources you used:
- Azure DevOps project "Pipeline Demo" (free tier).
- GitHub repository azure-pipeline-demo (free).
- Azure App Service (F1 free tier) from the Secret Mission.
- Local project folder azure-pipeline-demo on your Desktop.
Keep everything running
No action needed. Choose this if you are still actively building on this project or plan to extend it further.
- Your pipeline will continue to trigger on pushes to main, consuming free minutes (1,800 per month).
- Your Azure App Service stays live on the F1 free tier at no cost.
- Your GitHub repository remains accessible.
Pause - I'll come back to this later
Disable pipeline triggers to stop consuming free minutes while keeping everything in place.
- In Azure DevOps, open your pipeline in the "Pipeline Demo" project.
- Click the three-dot menu (...) at the top right, then select Settings.
- Toggle the pipeline to Paused. This stops all automatic triggers while preserving your configuration.
- Your Azure App Service, GitHub repository, and local files all remain intact for when you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
Delete the Azure App Service (Secret Mission):
- Open the Azure Portal at portal.azure.com.
- Navigate to App Services and select the Web App you created.
- On the app's Overview page, click Delete in the top toolbar.
- Confirm deletion by typing the app name and clicking Delete.
- Also delete the associated App Service Plan if prompted, to fully clean up.
Delete the Azure DevOps project:
- Go to dev.azure.com and sign in to your organization.
- Click Organization settings (gear icon in the bottom left).
- Select Projects in the left sidebar.
- Check the box next to "Pipeline Demo" and click Delete.
- Type the project name to confirm and click Delete.
Delete the GitHub repository:
- On GitHub, navigate to your azure-pipeline-demo repository.
- Click Settings (tab at the top of the repo page).
- Scroll down to the Danger Zone section and click Delete this repository.
- Follow the confirmation prompts. Type the repository name to verify and click Delete this repository.
Delete the local project folder:
- Delete your local azure-pipeline-demo folder by running the appropriate command for your operating system:
macOS
rm -rf ~/Desktop/azure-pipeline-demo
Windows
rmdir /s /q %USERPROFILE%\Desktop\azure-pipeline-demo
Nice Work!
Nice Work!
You just built a fully automated CI/CD pipeline from scratch using Azure DevOps. Every time you push code to main, your pipeline automatically builds, tests, and validates your application without any manual intervention.
You've learned how to:
- Built a Node.js application with automated Jest tests and pushed it to a GitHub repository.
- Connected Azure DevOps to GitHub and wrote a multi-stage YAML pipeline with automatic triggers that fire on every push to main.
- Published build artifacts and test results in Azure DevOps, proving your CI pipeline catches issues on every code change.
- Added a Deployment stage with a manual approval gate to push your app live to Azure App Service.
Ready to quiz yourself?