Azure DevOps Pipelines --- Alternative Migration Path¶
Audience: DevOps Engineer, Platform Engineer, Engineering Manager Reading time: 12 minutes Last updated: 2026-04-30
Overview¶
While GitHub Actions is the recommended CI/CD platform for CSA-in-a-Box, Azure DevOps Pipelines is a strong alternative for organizations with existing Azure DevOps investment, source code on Azure Repos, or requirements for Azure DevOps Server in on-premises IL4/IL5 environments. This guide covers migrating Jenkins pipelines to Azure DevOps Pipelines, including YAML pipeline structure, service connections, variable groups, deployment environments, and release gates.
1. When to choose Azure DevOps over GitHub Actions¶
| Scenario | Recommendation |
|---|---|
| Source code on Azure Repos, no plans to move | Azure DevOps |
| Heavy use of Azure Boards for project management | Azure DevOps (tighter integration) |
| Azure DevOps Server on-premises for IL4/IL5 | Azure DevOps (only option for on-prem ADO) |
| Need Azure Test Plans integration | Azure DevOps |
| Significant existing ADO pipeline investment | Azure DevOps (incremental migration) |
| Need release gates with business-hour windows | Azure DevOps (more mature gates) |
| Source code on GitHub | GitHub Actions (better integration) |
| Want Copilot CI/CD integration | GitHub Actions |
| Want largest marketplace ecosystem | GitHub Actions |
| Building CSA-in-a-Box reference implementation | GitHub Actions (native alignment) |
2. Jenkinsfile to Azure Pipelines YAML¶
Basic pipeline structure¶
trigger:
branches:
include:
- main
- develop
pool:
vmImage: ubuntu-latest
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npm ci && npm run build
displayName: Build
- stage: Test
dependsOn: Build
jobs:
- job: TestJob
steps:
- script: npm test
displayName: Test
- stage: Deploy
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployJob
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'my-azure-connection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group create \
--resource-group rg-csa-prod \
--template-file main.bicep
Key syntax differences¶
| Concept | Jenkins | Azure DevOps |
|---|---|---|
| Pipeline file | Jenkinsfile | azure-pipelines.yml |
| Triggers | SCM polling or webhook | trigger: for CI, pr: for PR validation |
| Build agent | agent any or agent { label '...' } | pool: vmImage: ubuntu-latest or pool: name: MyPool |
| Stages | stages { stage('Name') { } } | stages: - stage: Name |
| Steps | steps { sh '...' } | steps: - script: ... or - task: TaskName@version |
| Conditions | when { branch 'main' } | condition: eq(variables['Build.SourceBranch'], ...) |
| Parallel | parallel { } | Multiple jobs in same stage (parallel by default) |
| Parameters | parameters { string(...) } | parameters: - name: ... type: string |
| Environment variables | environment { VAR = 'val' } | variables: VAR: val |
| Credentials | credentials('id') | Service connections + variable groups |
| Post actions | post { always { } } | condition: always() on step, or always() in condition |
3. Service connections --- Replacing Jenkins credentials¶
Azure DevOps uses service connections to authenticate to external services. This is the equivalent of Jenkins credentials but with a more structured approach.
Azure Resource Manager service connection¶
# Reference in pipeline
- task: AzureCLI@2
inputs:
azureSubscription: "csa-inabox-production" # Service connection name
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group create ...
Setup:
- Go to Project Settings > Service connections > New service connection
- Select Azure Resource Manager
- Choose Workload Identity Federation (automatic) for OIDC (recommended)
- Or choose Service principal (manual) for traditional SP authentication
- Scope to a specific subscription and resource group
Workload Identity Federation (OIDC for Azure DevOps)¶
Azure DevOps supports OIDC federation similar to GitHub Actions, eliminating stored service principal secrets.
# Workload Identity Federation service connection
- task: AzureCLI@2
inputs:
azureSubscription: "csa-oidc-connection" # WIF-based connection
scriptType: bash
scriptLocation: inlineScript
inlineScript: az account show
Other service connection types¶
| Jenkins credential | Azure DevOps service connection |
|---|---|
| Docker registry credentials | Docker Registry service connection |
| SSH key | SSH service connection |
| Generic secret | Generic service connection |
| Kubernetes config | Kubernetes service connection |
| GitHub token | GitHub service connection |
| SonarQube token | SonarQube service connection |
4. Variable groups --- Centralized secret management¶
Variable groups in Azure DevOps provide reusable sets of variables across pipelines, similar to Jenkins folder-scoped credentials.
Creating a variable group¶
# Reference in pipeline
variables:
- group: csa-inabox-secrets # Variable group name
- name: ENVIRONMENT
value: production
Linking to Azure Key Vault¶
Variable groups can pull secrets directly from Azure Key Vault at pipeline runtime:
- Create variable group linked to Key Vault
- Select secrets to expose as pipeline variables
- Reference in pipeline --- secrets are injected as variables
variables:
- group: csa-keyvault-secrets # Linked to Azure Key Vault
steps:
- script: echo "DB connection available"
env:
DB_CONN: $(db-connection-string) # From Key Vault
This eliminates stored secrets in Azure DevOps entirely --- secrets remain in Key Vault and are fetched at runtime.
5. Deployment environments¶
Azure DevOps environments provide deployment tracking, approval gates, and resource targeting.
Environment configuration¶
stages:
- stage: DeployStaging
jobs:
- deployment: DeployToStaging
environment: staging
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to staging"
- stage: DeployProduction
dependsOn: DeployStaging
jobs:
- deployment: DeployToProduction
environment: production # Requires approval
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to production"
Approval gates¶
Configure approvals in Pipelines > Environments > production > Approvals and checks:
| Check type | Description | Jenkins equivalent |
|---|---|---|
| Approvals | Require specific users/groups to approve | input message: '...' |
| Branch control | Only allow deployments from specific branches | when { branch 'main' } |
| Business hours | Only allow deployments during specified hours | No direct equivalent |
| Template | Require pipeline to extend a specific template | No direct equivalent |
| Invoke Azure function | Custom validation via serverless function | Custom scripted stage |
| Query Azure Monitor alerts | Block deployment if active alerts exist | No direct equivalent |
| Required template | Enforce pipeline structure governance | Shared library structure |
Deployment strategies¶
# Rolling deployment
strategy:
rolling:
maxParallel: 2
deploy:
steps:
- script: deploy-to-node.sh
# Canary deployment
strategy:
canary:
increments: [10, 20, 50, 100]
deploy:
steps:
- script: deploy-canary.sh
on:
success:
steps:
- script: promote-canary.sh
failure:
steps:
- script: rollback-canary.sh
6. Templates --- Replacing Jenkins shared libraries¶
Azure DevOps templates serve the same purpose as Jenkins shared libraries --- reusable pipeline components.
Step template¶
# templates/bicep-deploy.yml
parameters:
- name: environment
type: string
- name: resourceGroup
type: string
- name: serviceConnection
type: string
steps:
- task: AzureCLI@2
inputs:
azureSubscription: ${{ parameters.serviceConnection }}
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group create \
--resource-group ${{ parameters.resourceGroup }} \
--template-file infra/main.bicep \
--parameters environment=${{ parameters.environment }}
Using templates¶
# azure-pipelines.yml
stages:
- stage: DeployDev
jobs:
- job: Deploy
steps:
- template: templates/bicep-deploy.yml
parameters:
environment: dev
resourceGroup: rg-csa-dev
serviceConnection: csa-dev-connection
- stage: DeployProd
dependsOn: DeployDev
jobs:
- deployment: Deploy
environment: production
strategy:
runOnce:
deploy:
steps:
- template: templates/bicep-deploy.yml
parameters:
environment: prod
resourceGroup: rg-csa-prod
serviceConnection: csa-prod-connection
Extends templates (governance)¶
# templates/governed-pipeline.yml (in central repo)
parameters:
- name: buildSteps
type: stepList
default: []
stages:
- stage: SecurityScan
jobs:
- job: Scan
steps:
- task: CredScan@3 # Enforced security scan
- task: SdtReport@2
- stage: Build
dependsOn: SecurityScan
jobs:
- job: Build
steps: ${{ parameters.buildSteps }}
# Team pipeline (must extend governed template)
extends:
template: templates/governed-pipeline.yml@central-repo
parameters:
buildSteps:
- script: npm ci && npm run build
- script: npm test
7. Matrix builds¶
strategy:
matrix:
linux_node18:
vmImage: ubuntu-latest
nodeVersion: 18
linux_node20:
vmImage: ubuntu-latest
nodeVersion: 20
windows_node20:
vmImage: windows-latest
nodeVersion: 20
maxParallel: 3
pool:
vmImage: $(vmImage)
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- script: npm ci && npm test
8. Azure DevOps CLI and automation¶
# Create a pipeline
az pipelines create \
--name "CSA-in-a-Box Deploy" \
--repository csa-inabox \
--branch main \
--yml-path azure-pipelines.yml
# Run a pipeline
az pipelines run --name "CSA-in-a-Box Deploy"
# List pipeline runs
az pipelines runs list --pipeline-id 42 --top 10
# Create a variable group
az pipelines variable-group create \
--name "csa-secrets" \
--variables API_KEY=secret DB_HOST=server.database.windows.net
9. Azure DevOps for CSA-in-a-Box¶
While CSA-in-a-Box natively uses GitHub Actions, the same CI/CD patterns translate to Azure DevOps Pipelines:
| CSA-in-a-Box pattern | Azure DevOps implementation |
|---|---|
| Bicep What-If | AzureCLI@2 task with az deployment group what-if |
| Bicep Deploy | AzureResourceManagerTemplateDeployment@3 task |
| dbt Test | Script tasks with pip install dbt-databricks && dbt test |
| Compliance Check | ms-devlabs.custom-terraform-tasks.custom-terraform-release-task.TerraformTaskV4@4 for Checkov |
| MkDocs Deploy | Script task with mkdocs build + Azure Static Web Apps deployment |
| OIDC Auth | Workload Identity Federation service connection |
Example: CSA-in-a-Box on Azure DevOps¶
trigger:
branches:
include: [main]
pr:
branches:
include: [main]
variables:
- group: csa-inabox-config
stages:
- stage: Validate
jobs:
- job: BicepWhatIf
pool:
vmImage: ubuntu-latest
steps:
- task: AzureCLI@2
inputs:
azureSubscription: csa-oidc
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group what-if \
--resource-group $(resourceGroup) \
--template-file infra/main.bicep
- job: dbtTest
pool:
vmImage: ubuntu-latest
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "3.11"
- script: |
pip install dbt-databricks
dbt deps && dbt test
displayName: dbt test
- stage: Deploy
dependsOn: Validate
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: BicepDeploy
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
azureResourceManagerConnection: csa-oidc
resourceGroupName: $(resourceGroup)
location: eastus
templateLocation: "Linked artifact"
csmFile: infra/main.bicep
10. Comparison summary¶
| Dimension | Azure DevOps Pipelines | GitHub Actions |
|---|---|---|
| Syntax readability | Verbose (tasks with inputs) | Concise (uses/run) |
| Learning curve | Steeper (tasks, service connections, variable groups) | Gentler (YAML + marketplace) |
| Template governance | extends keyword (strong governance) | Reusable workflows (lighter governance) |
| Deployment strategies | Rolling, canary, blue-green (native) | Environment protection rules (simpler) |
| Approval gates | Rich (business hours, Azure Monitor, functions) | Environment required reviewers + wait timer |
| Marketplace | ~1,200 extensions | 20,000+ actions |
| AI assistance | Limited | Copilot (native) |
| On-premises option | Azure DevOps Server | GitHub Enterprise Server |
| Federal coverage | FedRAMP High (Azure DevOps Service) | GHEC with data residency |
Next steps¶
- Decide between GitHub Actions and Azure DevOps --- Use the decision matrix in the Migration Center.
- If choosing Azure DevOps --- Set up a project, create service connections, and convert your first pipeline using this guide.
- If choosing GitHub Actions --- Follow the Pipeline Migration Guide.
- For federal requirements --- See the Federal Migration Guide for both platforms.