Build a CI/CD Pipeline with Azure DevOps
Automate building and deploying a Node.js app with Azure DevOps Pipelines.
Introduction
30 Second Summary
Every time a developer finishes writing code, someone has to get that code onto the live server where users can see it. Doing this manually is slow, error-prone, and stressful.
In this project, you will build an automated pipeline that takes your code from GitHub, builds it, and deploys it to a live website. Every time you push a change, the pipeline handles the rest.
What You'll Build
A complete CI/CD pipeline using Azure DevOps that automatically builds and deploys a Node.js web application to Azure App Service every time you push code to the main branch.
By the end of this project, you'll have:
- A multi-stage YAML pipeline that builds your app and deploys it to a live URL automatically.
- A service connection linking Azure DevOps to your Azure subscription for secure, automated deployments.
- A verified CI trigger that fires a new pipeline run every time you push a commit to your repository.
- Secret Mission: Add a staging environment with deployment slots and implement zero-downtime slot swaps.
Are there any prerequisites?
You need a GitHub account, a Microsoft account (for Azure DevOps and Azure Portal), and an active Azure subscription (Pay-As-You-Go or Enterprise Agreement). The Azure Free Trial is not supported for Azure DevOps billing.
If your Azure DevOps organization is brand new, you must request the free parallel job grant at https://aka.ms/azpipelines-parallelism-request before your pipeline can run. Approval takes 2-3 business days, so submit this request early.
Set Up Azure DevOps and Fork the Repository
Before you can build a pipeline, you need the two platforms that make it all work.
GitHub holds your source code, and Azure DevOps watches that code for changes and runs your pipeline. In this step, you'll set up both by forking a sample app and creating your Azure DevOps project.
In this step, get ready to:
- Fork the Microsoft sample Node.js app into your GitHub account.
- Create an Azure DevOps organization and project.
- Verify your Azure subscription is ready for the pipeline.
Fork the sample Node.js application
Microsoft maintains a minimal Node.js Express app specifically designed for Azure Pipelines tutorials. You'll fork this repository so you have your own copy to connect to your pipeline later.
- Open your web browser and go to the sample repository:
https://github.com/Azure-Samples/nodejs-docs-hello-world
- Click the Fork button in the top-right corner of the repository page.
- On the "Create a new fork" page, leave all defaults and click Create fork.
GitHub redirects you to your new fork. The URL should look like https://github.com/<your-username>/nodejs-docs-hello-world.
What is this sample app?
The nodejs-docs-hello-world app is a minimal Express server that displays "Hello World!" in the browser. It has no complex build steps or dependencies, so you can focus entirely on learning the pipeline itself.
Create your Azure DevOps organization and project
An Azure DevOps organization is your top-level container. Inside it, you create projects that hold your pipelines, repositories, and boards.
- Open a new browser tab and go to dev.azure.com.
- Sign in with your Microsoft account.
- If you do not already have an organization, Azure DevOps prompts you to create one. Enter a name for your organization and select a region closest to you.
- Click Continue to finish creating the organization.
Now create a project inside your new organization.
- Click New project in the top-right corner of the organization page.
- Enter cicd-pipeline-project as the project name.
- Select your preferred visibility (Private is fine for this project).
- Click Create.
You should see your new project's dashboard with sections for Boards, Repos, Pipelines, and more in the left sidebar.
New organization? Request the free parallel job grant
New Azure DevOps organizations do not automatically receive the free parallel job grant (this was disabled due to abuse). Without it, your pipelines cannot run.
Submit a request at https://aka.ms/azpipelines-parallelism-request. Approval takes 2-3 business days. You can continue setting up everything else in this project while you wait.
What is a parallel job?
A parallel job is a slot that lets Azure DevOps run one pipeline at a time. The free tier gives you 1 Microsoft-hosted parallel job with 1,800 minutes per month for private projects. That is more than enough for this project.
Verify Azure subscription access
Your pipeline will deploy to Azure App Service, which requires an active Azure subscription. Let's confirm yours is ready.
- Open a new browser tab and go to the Azure Portal at portal.azure.com.
- Sign in with your Microsoft account.
- In the search bar at the top, type Subscriptions and select Subscriptions from the results.
- Confirm that you see at least one subscription with a status of Active.
- Verify the subscription type is Pay-As-You-Go or Enterprise Agreement (not Free Trial).
Azure Free Trial is not supported
Azure DevOps billing does not support Azure Free Trial subscriptions. If your subscription says "Free Trial", you'll need to upgrade to Pay-As-You-Go before continuing. The Azure App Service Free tier used in this project incurs no charges.
Don't see an active subscription?
If you see no subscriptions listed, make sure you are signed in with the same Microsoft account you used for Azure. If you need to create a Pay-As-You-Go subscription, Azure will prompt you through the process on the Subscriptions page.
Help me figure out why my Azure subscription isn't showing up.
Your accounts and repository are ready. Next up, you'll use Azure Cloud Shell to deploy the sample app so your pipeline has something to target.
Create the Azure App Service Web App
Your pipeline needs a live destination to deploy code to.
Azure App Service is a managed hosting platform that runs web apps without you managing servers. In this step, you'll use Azure Cloud Shell to deploy your forked sample app as a live web app on the Free tier.
In this step, get ready to:
- Open Azure Cloud Shell in the Azure Portal.
- Deploy the sample Node.js app to Azure App Service.
- Verify the live app in your browser.
Open Azure Cloud Shell
Cloud Shell is a browser-based terminal built into the Azure Portal. It comes pre-installed with the Azure CLI, so you can manage Azure resources without installing anything locally.
- In the Azure Portal, look at the top toolbar.
- Click the Cloud Shell icon (it looks like >_) in the top-right area of the toolbar.
- If prompted to choose a shell type, select Bash.
- If prompted to create a storage account, click Create storage and wait for the shell to initialize.
A terminal panel opens at the bottom of your browser window. You should see a command prompt ready for input.
What is Azure Cloud Shell?
Cloud Shell is a free, browser-based terminal that runs inside the Azure Portal. It has the Azure CLI pre-installed and is automatically authenticated with your Azure account.
This means you can run Azure commands immediately without installing anything on your computer.
Clone and deploy the sample app
You need a copy of your forked repository inside Cloud Shell so that the Azure CLI can detect the app and deploy it.
- Clone your forked repository into Cloud Shell by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/nodejs-docs-hello-world.git
- Move into the cloned project folder by running:
cd nodejs-docs-hello-world
Your terminal prompt should now show nodejs-docs-hello-world in the path, confirming you are inside the project.
Now you'll deploy this app to Azure App Service. The az webapp up command detects the app type, provisions the hosting infrastructure, and deploys your code in one step.
- Deploy the app to Azure App Service on the Free tier by running this command:
az webapp up --sku F1 --name [[APPNAME="your-unique-app-name"]]
What does this command do?
- az webapp up is an all-in-one command that provisions and deploys your app.
- --sku F1 selects the Free tier App Service plan, so there are no charges.
- --name sets a globally unique name for your web app. This becomes part of your live URL.
- The command automatically detects it is a Node.js app, creates a resource group, creates an App Service plan, and deploys the code.
Pick a unique app name
The app name must be globally unique across all of Azure. Try including your name or initials, like nextwork-hello-jane. If the name is taken, the command will tell you and you can try another.
This command takes 1-2 minutes to complete. When it finishes, look for a line in the output that shows your app URL in the format https://<your-app-name>.azurewebsites.net.
Seeing an error about the app name?
If the command fails because the name is already taken, choose a different unique name and run the command again.
If you see a subscription-related error, confirm you are using a Pay-As-You-Go or Enterprise Agreement subscription. Azure Free Trial is not supported for this.
Help me troubleshoot my az webapp up command
Verify the app is running
- Copy the URL from the command output (e.g., https://<your-app-name>.azurewebsites.net).
- Open a new browser tab and paste the URL.
You should see a page that says "Hello World!" in your browser. This confirms your app is live on Azure App Service.
Not seeing the Hello World page?
Give it 30 seconds and refresh the page. New App Service apps can take a moment to start up for the first time.
Double check you copied the full URL including https:// at the beginning.
Help me get my App Service web app to load
Your app is live and ready to receive deployments. Next up, you'll connect Azure DevOps to your Azure subscription so the pipeline can deploy to this app automatically.
Connect Azure DevOps to Your Azure Subscription
Your pipeline needs permission to deploy code to your Azure App Service. Without a trusted link between Azure DevOps and your Azure subscription, the pipeline has no way to reach your resources.
The bridge between Azure DevOps and Azure is called a service connection. In this step, you will create one that grants your pipeline the ability to deploy directly to your web app.
In this step, get ready to:
- Create a service connection in your Azure DevOps project.
- Verify the connection is authenticated and ready to use.
Create a service connection
A service connection authenticates Azure DevOps with your Azure subscription using a service principal. The automatic method handles all the credential setup for you.
- In your Azure DevOps project, click the Project settings gear icon in the bottom-left corner of the sidebar.
- In the left menu under Pipelines, click Service connections.
- Click New service connection.
- Select Azure Resource Manager from the list, then click Next.
- Choose the Service principal (automatic) authentication method, then click Next.
- For Scope level, select Subscription.
- Select your Azure subscription from the dropdown.
- Select the resource group that contains your App Service (the one created by az webapp up in the previous step).
What is a service connection?
A service connection is an authenticated link between Azure DevOps and an external service. It stores credentials securely so your pipeline can deploy code without you manually signing in each time.
The "Service principal (automatic)" method creates a service principal in Microsoft Entra ID automatically. Azure DevOps handles all the credential setup behind the scenes and grants it Contributor access to your selected resource group.
Don't see your subscription in the dropdown?
Make sure you are signed into Azure DevOps with the same Microsoft account that owns your Azure subscription. If you are using an Azure Free Trial subscription, it is not supported for Azure DevOps billing. You need a Pay-As-You-Go or Enterprise Agreement subscription.
help me troubleshoot why my Azure subscription doesn't appear in the service connection dropdown
Name and verify the connection
- In the Service connection name field, enter azure-app-service-connection.
- Check the Grant access permission to all pipelines checkbox.
- Click Save.
After a few seconds, you should see your new azure-app-service-connection listed on the Service connections page.
Why does the name matter?
The service connection name is what your pipeline YAML file uses to reference this connection. In the next step, you will see azureSubscription: 'azure-app-service-connection' in the pipeline configuration. The name must match exactly.
Seeing an authorization error?
If saving fails with a permissions error, your Azure account may not have permission to create applications in Microsoft Entra ID. Ask your directory administrator to grant you the ability to register applications, or have them create the service principal manually.
help me fix an authorization error when creating a service connection in Azure DevOps
Your Azure DevOps project now has a secure link to your Azure subscription. Next up, you will create the pipeline itself and watch it build and deploy your app automatically.
Create the CI/CD Pipeline
Your Azure DevOps project is connected to your Azure subscription. The missing piece is the pipeline itself.
A CI/CD pipeline is a set of automated instructions that build your code and deploy it every time you push a change. In this step, you will create a YAML pipeline that builds your Node.js app and deploys it to Azure App Service.
In this step, get ready to:
- Start a new pipeline and connect it to your GitHub repository.
- Select the Node.js deployment template to generate pipeline YAML.
- Review the generated pipeline and trigger your first run.
Start pipeline creation
You will point Azure DevOps at your forked GitHub repository so it knows where to pull your source code from.
- In your Azure DevOps project, click Pipelines in the left sidebar.
- Click New pipeline (or Create Pipeline if this is your first pipeline).
- Under Where is your code?, select GitHub.
- If prompted, authorize Azure Pipelines to access your GitHub account by following the OAuth flow.
- Select your forked nodejs-docs-hello-world repository from the list.
Why GitHub as the source?
Azure Pipelines pulls your code from the repository you select here. Each time you push a commit to this repository, the pipeline triggers automatically.
Select the Node.js pipeline template
Azure Pipelines analyzes your repository and suggests a template based on the detected language. For a Node.js app targeting Linux on Azure, it recommends a multi-stage template with Build and Deploy stages.
- Azure Pipelines will display a list of suggested templates. Select Node.js Express Web App to Linux on Azure.
- When prompted, select your Azure subscription from the dropdown.
- Select the web app you created earlier (the one named after your unique app name).
- Click Validate and configure.
Azure DevOps generates an azure-pipelines.yml file and displays it in the YAML editor.
What is this template doing?
The template creates a complete pipeline definition file called azure-pipelines.yml. This file lives in your repository and tells Azure Pipelines exactly how to build and deploy your app.
The template pre-fills your subscription and app name into the YAML variables so the pipeline knows where to deploy.
Review and run the pipeline
Before running, take a moment to understand the two stages in the generated YAML. The pipeline hierarchy follows a structure of triggers, stages, jobs, and steps.
Your generated YAML should look similar to this:
trigger:
- main
variables:
azureSubscription: 'azure-app-service-connection'
webAppName: 'your-unique-app-name'
environmentName: 'your-unique-app-name'
vmImageName: 'ubuntu-latest'
stages:
- stage: Build
displayName: 'Build stage'
jobs:
- job: Build
displayName: 'Build'
pool:
vmImage: $(vmImageName)
steps:
- task: UseNode@1
inputs:
version: '18.x'
displayName: 'Install Node.js'
- script: |
npm install
displayName: 'npm install'
- task: ArchiveFiles@2
displayName: 'Archive files'
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
replaceExistingArchive: true
- upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
artifact: drop
- stage: Deploy
displayName: 'Deploy stage'
dependsOn: Build
condition: succeeded()
jobs:
- deployment: Deploy
displayName: 'Deploy'
environment: $(environmentName)
pool:
vmImage: $(vmImageName)
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
displayName: 'Azure Web App Deploy'
inputs:
azureSubscription: $(azureSubscription)
appType: 'webAppLinux'
appName: $(webAppName)
package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
What does this pipeline do?
The Build stage installs Node.js 18.x on the build agent, runs npm install to fetch dependencies, archives the entire project into a zip file, and publishes it as an artifact named drop.
The Deploy stage only runs if the Build stage succeeded. It uses the AzureWebApp@1 task to take that zip artifact and deploy it directly to your Azure App Service web app.
The trigger: main line at the top means this entire pipeline runs automatically whenever you push a commit to the main branch.
What are the variables section values?
The azureSubscription variable references the service connection you created in the previous step (azure-app-service-connection). The webAppName and environmentName values match your App Service web app name.
The vmImageName sets ubuntu-latest as the operating system for the build agent that runs your pipeline tasks.
- Confirm the azureSubscription value in the YAML matches your service connection name (azure-app-service-connection).
- Confirm the webAppName value matches the App Service name you created earlier.
- Click Save and run in the top right corner.
What happens when I click Save and run?
Azure DevOps will prompt you to commit the azure-pipelines.yml file to your repository. You can commit directly to the main branch or create a new branch.
- In the commit dialog, leave the default option to commit directly to the main branch.
- Click Save and run again to confirm.
Your first pipeline run starts immediately. You should see the pipeline UI with both the Build and Deploy stages displayed.
Pipeline stuck on queuing?
If your pipeline stays in a queued state and never starts, your organization may not have the free parallel job grant yet. New Azure DevOps organizations must request it at https://aka.ms/azpipelines-parallelism-request and wait 2-3 business days for approval.
If the pipeline fails at the Deploy stage, double-check that your service connection name in the YAML exactly matches the name you gave it in Project Settings.
Help me troubleshoot my pipeline run that is stuck or failing.
Your pipeline is now running its first build and deploy cycle. Next up, you will verify the deployment went live and test the CI trigger by pushing a code change.
Verify the Deployment and Trigger CI
Your pipeline is running. The Build stage installs dependencies and packages your app, then the Deploy stage pushes it to Azure App Service. But how do you know it actually worked?
In this step, you will monitor the pipeline run in Azure DevOps, confirm the live deployment in your browser, and then prove that CI triggers work by pushing a code change to GitHub.
In this step, get ready to:
- Monitor the Build and Deploy stages in the pipeline UI.
- Verify the live app deployed through the pipeline.
- Trigger an automatic CI build by pushing a code change.
Monitor the pipeline run
The pipeline you triggered in the last step should already be running (or finished). You can watch each stage execute in real time and inspect the logs for any issues.
- In your Azure DevOps project, click Pipelines in the left sidebar.
- Click on your pipeline run (the most recent entry at the top of the list).
- You should see two stages displayed: Build stage and Deploy stage.
- Click on the Build stage to expand it and view the individual step logs.
- Click on the Deploy stage to view its logs.
- Once both stages show green checkmarks, the run is complete.
What should I see in the logs?
The Build stage logs show Node.js being installed, npm install running, and the project files being archived into a zip artifact.
The Deploy stage logs show the Azure Web App Deploy task uploading the zip to your App Service. This task output also displays the App Service URL where your app is live.
- In the Deploy stage, click the Azure Web App Deploy task to expand its output.
- Find the App Service URL in the task output (it looks like https://<your-app-name>.azurewebsites.net).
Pipeline run stuck or failed?
If the run is stuck with no agent assigned, your organization may not have the free parallel job grant yet. Check your request status at https://aka.ms/azpipelines-parallelism-request.
If the Deploy stage failed, verify that your service connection name in the YAML (azure-app-service-connection) matches the name you gave it in Project Settings.
help me debug my failed Azure DevOps pipeline run
Verify the live deployment
- Copy the App Service URL from the deploy task output.
- Paste it into a new browser tab and press Enter.
You should see the "Hello World!" page. This confirms that the pipeline successfully built your app and deployed it to Azure App Service.
Seeing a default Azure page instead?
If you see a default Azure placeholder page, the deployment may still be in progress. Wait a minute and refresh. App Service can take a moment to swap in the new code after the deploy task completes.
help me troubleshoot my Azure App Service not showing the deployed app
Trigger a CI build
The real power of CI is that the pipeline runs automatically every time you push code. Your azure-pipelines.yml file has trigger: main configured, which means any commit to the main branch fires a new pipeline run. Let's prove it works.
- Open your forked nodejs-docs-hello-world repository on GitHub.
- Click on the README.md file.
- Click the pencil icon (edit button) in the top right of the file view.
- Add a new line to the file, such as: Deployed via Azure DevOps CI/CD pipeline.
- Click Commit changes.
- In the dialog, keep Commit directly to the main branch selected.
- Click Commit changes to confirm.
Now switch back to Azure DevOps to watch the CI trigger fire.
- In Azure DevOps, click Pipelines in the left sidebar.
- Look for a new pipeline run at the top of the list.
- Verify the new run shows the description Individual CI.
- Wait for both stages to complete with green checkmarks.
What does Individual CI mean?
Azure DevOps labels pipeline runs triggered by a single commit as "Individual CI". This confirms that the trigger in your YAML file is working correctly. Every push to main automatically kicks off the full build-and-deploy cycle.
No new pipeline run appearing?
Make sure you committed directly to the main branch, not a feature branch. The trigger in your YAML only watches main.
If you still don't see a new run after a minute, check that Azure DevOps still has access to your GitHub repo. Go to Project Settings > Service connections and verify the GitHub connection is still authorized.
help me figure out why my CI trigger isn't firing in Azure DevOps
What is the YAML pipeline hierarchy?
Your pipeline follows a four-level structure: triggers, stages, jobs, and steps. The trigger watches for commits to main. Each stage groups related work (Build vs Deploy). Each stage has one or more jobs, and each job contains the individual steps that run on the agent.
This hierarchy lets you scale your pipeline later by adding more stages (like testing or staging) without restructuring what you already have.
Secret mission
Add a Staging Slot with Slot Swap
Your pipeline deploys straight to production right now. What happens if a bad deployment takes down your site? In this secret mission, you will add a staging environment using Azure App Service deployment slots and implement a slot swap strategy for zero-downtime deployments.
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 the Azure App Service Free F1 tier and Azure DevOps free tier, so there are no ongoing costs for the base project.
Cost warning
If you scaled up to the Standard tier for the Secret Mission (deployment slots), make sure you have scaled back down to Free or deleted the resource group. The Standard S1 plan costs approximately $0.075/hour while running.
Resources you used:
- Azure App Service web app (Free F1 tier).
- Azure App Service plan (Free tier).
- Azure DevOps organization and project (Free tier).
- Forked GitHub repository.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep referencing your pipeline.
- The Free F1 tier App Service has no ongoing cost.
- Your pipeline continues to run on each push to main. Azure DevOps provides 1,800 free minutes per month for private projects.
- Your forked GitHub repository remains in your account at no cost.
Pause - I'll come back to this later
Disable your pipeline trigger so no new deployments occur. Your App Service remains live but stops receiving updates.
- In Azure DevOps, select Pipelines in the left sidebar.
- Select your pipeline, then click the ellipsis button at the far right and select Settings.
- Select Disabled, then click Save.
- Your App Service stays live at its URL but no new pipeline runs will trigger.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
Delete the Azure resource group:
- Open Azure Cloud Shell in the Azure Portal (click the Cloud Shell icon in the top toolbar).
- Delete your resource group by running this command:
az group delete --name [[RESOURCE_GROUP="your-resource-group-name"]] --yes
- This deletes the App Service, App Service plan, and all resources inside the resource group.
Delete the Azure DevOps project:
- In your Azure DevOps project, click Project settings in the lower-left corner.
- Under Overview, scroll to the bottom of the page and click Delete.
- Type your project name to confirm, then click Delete.
Delete the forked GitHub repository:
- On GitHub, navigate to your forked repository.
- Click Settings (under the repository name).
- Scroll down to the Danger Zone section and click Delete this repository.
- Confirm by typing the repository name, then click Delete this repository.
Scaled up for the Secret Mission?
If you upgraded your App Service plan to Standard for the deployment slots mission, deleting the resource group above handles everything. If you chose not to delete, make sure you scale back down to Free (F1) in the Azure Portal under your App Service plan's pricing tier settings.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete CI/CD pipeline from scratch using Azure DevOps and deployed a live web app entirely through automation.
You've learned how to:
- Built and deployed a live Node.js web app to Azure App Service via a fully automated pipeline.
- Created a multi-stage YAML pipeline with Build and Deploy stages, connected to your Azure subscription through a service connection.
- Verified CI triggers fire automatically on every code push to main, proving your pipeline delivers changes without manual intervention.
- Secret Mission: Added zero-downtime deployments with staging slots and slot swaps.
Ready to quiz yourself?