Introduction
Azure DevOps enables full automation of the application lifecycle through powerful YAML pipelines. In 2026, multi-stage pipelines with built-in quality controls have become the standard for mature DevOps teams. This tutorial walks you through creating a production-ready CI/CD pipeline for a Node.js application deployed to Azure App Service. You will learn how to structure YAML files, manage variables and secrets, and implement deployment gates.
Prerequisites
- Azure DevOps account and active Azure subscription
- Azure CLI installed and configured
- Basic knowledge of YAML and Docker containers
- Simple Node.js application ready for deployment
Step 1: Initialize the Repository and Pipeline
Create a new repository in Azure DevOps and add the azure-pipelines.yml file at the root.
Basic Build Pipeline
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
azureSubscription: 'MyAzureConnection'
appName: 'myapp-prod'
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: |
npm ci
npm run build
displayName: 'Build application'This pipeline triggers a build on every push to main. It uses Node 20 and runs the build commands. The Azure connection name must be created manually in the project settings.
Step 2: Add Tests and Code Analysis
Integrate unit tests and SonarQube to ensure quality before any deployment.
Adding Tests and Quality Checks
- script: |
npm test -- --coverage
displayName: 'Run tests'
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarConnection'
scannerMode: 'CLI'
configMode: 'manual'
cliProjectKey: 'myapp'
- task: SonarQubeAnalyze@5Tests run with coverage enabled. SonarQube is configured to analyze the code. The pipeline will automatically fail if quality thresholds are not met.
Step 3: Staging Deployment
Add a deployment stage to the staging environment with manual approval.
Staging Deployment Stage
- stage: DeployStaging
dependsOn: Build
jobs:
- deployment: Deploy
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: $(azureSubscription)
appName: 'myapp-staging'
package: '$(System.DefaultWorkingDirectory)/dist'This stage uses the deployment job model with an Azure DevOps environment. It enables manual approvals and gates before production.
Step 4: Production Deployment
Finalize with a protected and conditioned production stage.
Production Stage with Condition
- stage: DeployProduction
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: Deploy
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: $(azureSubscription)
appName: $(appName)
package: '$(System.DefaultWorkingDirectory)/dist'The production stage runs only on main and after staging validation. Using variables makes the pipeline reusable across projects.
Step 5: Variables and Secrets
Externalize configuration into pipeline variables and Azure Key Vault.
Variable Configuration
variables:
- group: 'myapp-variables'
- name: 'azureSubscription'
value: 'MyAzureConnection'
steps:
- task: AzureKeyVault@2
inputs:
azureSubscription: $(azureSubscription)
KeyVault: 'myapp-kv'
SecretsFilter: '*'
RunAsPreJob: trueVariables are loaded from a shared variable group. Secrets are injected from Key Vault at runtime to avoid storing them in plain text.
Best Practices
- Always use deployment jobs for deployments
- Protect main branches with branch policies
- Externalize all configuration into variable groups
- Add explicit conditions on production stages
- Version Docker images with Build.BuildId
Common Mistakes to Avoid
- Forgetting the branch condition on production deployments
- Using overly long inline scripts instead of templates
- Not configuring approvals on critical environments
- Storing secrets directly in the YAML file
Going Further
Explore our advanced Azure DevOps and YAML pipelines training at learni-group.com/formations.