Skip to content

DevOps on Azure

First PublishedByAtif Alam

Azure provides two paths for CI/CD and DevOps: Azure DevOps (a full platform with repos, pipelines, boards, and artifacts) and GitHub (with GitHub Actions deploying to Azure). Many teams use a mix of both.

Azure DevOps is a suite of five tools at dev.azure.com:

ServiceWhat It DoesComparable To
Azure ReposGit repository hostingGitHub, GitLab
Azure PipelinesCI/CD pipelinesGitHub Actions, Jenkins
Azure BoardsWork item tracking (agile boards)Jira, GitHub Issues
Azure ArtifactsPackage feeds (npm, NuGet, pip, Maven)GitHub Packages, Artifactory
Azure Test PlansManual and exploratory testingTestRail

You can use any combination — e.g. GitHub for repos + Azure Pipelines for CI/CD, or Azure Repos + GitHub Actions.

Pipelines define your CI/CD workflow in YAML (or the visual editor). They run on Microsoft-hosted agents (managed VMs) or self-hosted agents (your own machines).

azure-pipelines.yml
trigger:
branches:
include:
- main
paths:
exclude:
- docs/**
pool:
vmImage: 'ubuntu-latest' # Microsoft-hosted agent
variables:
imageName: 'my-app'
registryUrl: 'myregistry.azurecr.io'
stages:
- stage: Build
jobs:
- job: BuildAndTest
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.12'
- script: |
pip install -r requirements.txt
pytest --junitxml=results.xml
displayName: 'Install and test'
- task: PublishTestResults@2
inputs:
testResultsFiles: 'results.xml'
- task: Docker@2
inputs:
containerRegistry: 'myACRConnection'
repository: $(imageName)
command: 'buildAndPush'
Dockerfile: '**/Dockerfile'
tags: |
$(Build.BuildId)
latest
- stage: Deploy
dependsOn: Build
condition: succeeded()
jobs:
- deployment: DeployToProduction
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebAppContainer@1
inputs:
azureSubscription: 'my-azure-connection'
appName: 'my-webapp'
containers: '$(registryUrl)/$(imageName):$(Build.BuildId)'
ConceptWhat It Is
TriggerWhat starts the pipeline (branch push, PR, schedule, manual)
PoolThe agent that runs the pipeline (Microsoft-hosted or self-hosted)
StageA logical group of jobs (e.g. Build, Test, Deploy to Staging, Deploy to Prod)
JobA set of steps that run on one agent
StepA single task or script
TaskA pre-built action (Docker build, deploy to App Service, run tests)
EnvironmentA deployment target with approvals and checks
VariablePipeline-level or stage-level variables
Service connectionStored credentials for connecting to Azure, Docker registries, etc.
# Branch trigger
trigger:
branches:
include: [main, release/*]
exclude: [feature/experimental]
# PR trigger
pr:
branches:
include: [main]
# Scheduled trigger
schedules:
- cron: "0 2 * * *"
displayName: "Nightly build"
branches:
include: [main]
# Manual trigger (no trigger key, run from UI)
trigger: none

Environments add gates and approvals to deployments:

- deployment: DeployToProduction
environment: 'production' # requires approval configured in Azure DevOps
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to production"

Configure approvals in Azure DevOps: Pipelines → Environments → production → Approvals and checks.

Check TypeWhat It Does
Manual approvalA person must approve before deployment continues
Branch protectionOnly allow deployments from specific branches
Business hoursOnly deploy during specified hours
TemplateRequire the pipeline to extend a specific template
# Inline variables
variables:
environment: production
region: eastus
# Variable groups (shared across pipelines, stored in Azure DevOps)
variables:
- group: production-secrets # contains DB_PASSWORD, API_KEY, etc.
# Secret variables (masked in logs)
variables:
- name: DB_PASSWORD
value: $(dbPassword) # from variable group or pipeline settings

Never hardcode secrets in YAML. Use variable groups linked to Azure Key Vault:

variables:
- group: production-secrets # linked to Key Vault — auto-syncs secrets
strategy:
# Simple: deploy once
runOnce:
deploy:
steps: [...]
# Rolling: deploy to N% of targets at a time
rolling:
maxParallel: 25% # 25% of targets at a time
deploy:
steps: [...]
# Canary: route a percentage of traffic, then proceed
canary:
increments: [10, 20] # 10%, then 20%, then full
deploy:
steps: [...]
templates/build.yml
parameters:
- name: runtime
type: string
default: 'python:3.12'
steps:
- script: |
pip install -r requirements.txt
pytest
displayName: 'Build and test'
azure-pipelines.yml
stages:
- stage: Build
jobs:
- job: Build
steps:
- template: templates/build.yml
parameters:
runtime: 'python:3.12'
Microsoft-HostedSelf-Hosted
SetupZero (managed by Microsoft)You install and maintain
OSUbuntu, Windows, macOSAny OS you control
Clean environmentFresh VM for every jobPersistent (faster, but state leaks)
Cost1,800 free minutes/month (private projects)Free (your infrastructure cost)
Use caseMost pipelinesNeed GPU, specific software, VNet access

Git repositories hosted in Azure DevOps. Supports branch policies, pull requests, and code reviews.

PolicyWhat It Does
Required reviewersPR must be approved by N reviewers
Build validationPR must pass a CI pipeline before merge
Comment resolutionAll PR comments must be resolved
Merge typeEnforce squash merge, rebase, etc.
Linked work itemsPR must be linked to a Board work item

ACR is Azure’s container registry — like AWS ECR:

Terminal window
# Create a registry
az acr create --resource-group myapp-rg --name myregistry --sku Standard
# Login
az acr login --name myregistry
# Build and push (ACR can build images for you — no local Docker needed)
az acr build --registry myregistry --image my-app:v1 .
# List images
az acr repository list --name myregistry --output table

ACR Tasks can build images in the cloud on push or on a schedule — no Docker on your CI agent.

Many teams use GitHub for code and GitHub Actions for CI/CD, deploying to Azure.

.github/workflows/deploy.yml
name: Deploy to Azure App Service
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login (OIDC — no secrets)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Build and push to ACR
run: |
az acr login --name myregistry
docker build -t myregistry.azurecr.io/my-app:${{ github.sha }} .
docker push myregistry.azurecr.io/my-app:${{ github.sha }}
- name: Deploy to App Service
uses: azure/webapps-deploy@v3
with:
app-name: my-webapp
images: myregistry.azurecr.io/my-app:${{ github.sha }}

Use federated credentials instead of storing client secrets:

  1. Create an app registration in Entra ID.
  2. Add a federated credential for GitHub.
  3. Assign the app a role (Contributor) on the resource group.
  4. Use azure/login with client-id, tenant-id, subscription-id.

No secrets to rotate or leak.

FeatureAzure DevOpsGitHub
ReposAzure ReposGitHub Repos
CI/CDAzure Pipelines (YAML)GitHub Actions
Work trackingAzure Boards (rich, Jira-like)GitHub Issues + Projects (simpler)
Package managementAzure ArtifactsGitHub Packages
Container registryACR (separate service)GitHub Container Registry / ACR
ApprovalsEnvironments + approvalsEnvironments + required reviewers
Best forEnterprise, complex pipelines, Microsoft shopsOpen source, startups, GitHub-centric teams

Both integrate with Azure equally well. The choice often comes down to where your code already lives.

  • Azure Pipelines supports multi-stage YAML pipelines with environments, approvals, and deployment strategies (rolling, canary).
  • Use variable groups linked to Key Vault for secrets — never hardcode them in YAML.
  • Environments with approval gates protect production deployments.
  • ACR stores container images. Use ACR Tasks to build images in the cloud without local Docker.
  • GitHub Actions with OIDC is a great alternative — use federated credentials for secret-free Azure authentication.
  • Use branch policies (Azure Repos) or branch protection rules (GitHub) to enforce code review and CI checks before merging.