Azure DevOps Services: Complete CI/CD and Project Management Guide
Azure DevOps Services provides a comprehensive suite of development tools including version control, CI/CD pipelines, project management, and testing capabilities. This guide covers everything needed to implement modern DevOps practices for small business development teams.
Understanding Azure DevOps Services
Core Services
- Azure Repos: Git-based version control
- Azure Pipelines: CI/CD automation
- Azure Boards: Project management and tracking
- Azure Test Plans: Manual and automated testing
- Azure Artifacts: Package management
Key Features
- Integrated toolchain: End-to-end development lifecycle
- Cloud and on-premises: Flexible deployment options
- Multi-language support: .NET, Java, Node.js, Python, and more
- Extensible: Rich marketplace of extensions
- Enterprise security: Advanced security and compliance features
Setting Up Azure DevOps Organization
Organization Creation
# Install Azure DevOps CLI
az extension add --name azure-devops
# Login to Azure DevOps
az devops login
# Create organization (done through web interface)
# Then configure organization settings
az devops configure --defaults organization=https://dev.azure.com/YourOrganization
# Create project
az devops project create --name "BusinessProject" --description "Main business application project" --visibility "private"
# Set default project
az devops configure --defaults project="BusinessProject"
User Management
# Add users to organization
az devops user add --email-id "developer@company.com" --license-type "basic"
az devops user add --email-id "tester@company.com" --license-type "basic"
# Create security groups
az devops security group create --name "Developers" --description "Development team members"
az devops security group create --name "Testers" --description "Testing team members"
# Add users to groups
az devops security group membership add --group-id "Developers" --member-id "developer@company.com"
az devops security group membership add --group-id "Testers" --member-id "tester@company.com"
Azure Repos Configuration
Git Repository Setup
# Initialize new repository
git init
git remote add origin https://YourOrganization@dev.azure.com/YourOrganization/BusinessProject/_git/BusinessApp
# Create repository structure
mkdir -p src/BusinessApp.Api
mkdir -p src/BusinessApp.Core
mkdir -p src/BusinessApp.Data
mkdir -p tests/BusinessApp.Tests
mkdir -p docs
mkdir -p scripts
# Create .gitignore
cat > .gitignore << 'EOF'
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# NuGet Packages
*.nupkg
**/packages/*
!**/packages/build/
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio folders
.vs/
.vscode/
# Environment files
.env
.env.local
.env.production
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
EOF
# Initial commit
git add .
git commit -m "Initial project structure"
git push -u origin main
Branch Policies
# Set branch policies using Azure CLI
az repos policy create --policy-type "build" --repository-id "BusinessApp" --branch "main" --blocking true --enabled true --build-definition-id "1"
# Require pull request reviews
az repos policy create --policy-type "minimum-reviewers" --repository-id "BusinessApp" --branch "main" --blocking true --enabled true --minimum-reviewer-count 2
# Work item linking policy
az repos policy create --policy-type "work-item-linking" --repository-id "BusinessApp" --branch "main" --blocking true --enabled true
Branch Strategy
# Git flow branch strategy
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
- feature/*
- release/*
- hotfix/*
pr:
branches:
include:
- main
- develop
variables:
buildConfiguration: 'Release'
dotNetFramework: 'net6.0'
dotNetVersion: '6.0.x'
pool:
vmImage: 'windows-latest'
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: Build
displayName: 'Build Job'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK'
inputs:
packageType: sdk
version: $(dotNetVersion)
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests*.csproj'
arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage"'
- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
Azure Pipelines Implementation
Build Pipeline
# Build pipeline for .NET application
name: $(Build.DefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)
trigger:
branches:
include:
- main
- develop
variables:
buildConfiguration: 'Release'
vmImageName: 'windows-latest'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: Build
displayName: 'Build'
pool:
vmImage: $(vmImageName)
steps:
- checkout: self
fetchDepth: 0
- task: UseDotNet@2
displayName: 'Use .NET 6.0'
inputs:
packageType: 'sdk'
version: '6.0.x'
- task: DotNetCoreCLI@2
displayName: 'Restore NuGet packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
feedsToUse: 'select'
vstsFeed: 'BusinessProject/BusinessPackages'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Run unit tests'
inputs:
command: 'test'
projects: '**/*Tests*.csproj'
arguments: '--configuration $(buildConfiguration) --no-build --collect:"XPlat Code Coverage" --results-directory $(Agent.TempDirectory)'
- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage results'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
- task: DotNetCoreCLI@2
displayName: 'Publish application'
inputs:
command: 'publish'
projects: '**/BusinessApp.Api.csproj'
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: true
- task: PublishBuildArtifacts@1
displayName: 'Publish build artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
- stage: QualityGate
displayName: 'Quality Gate'
dependsOn: Build
condition: succeeded()
jobs:
- job: SonarQube
displayName: 'SonarQube Analysis'
pool:
vmImage: $(vmImageName)
steps:
- task: SonarQubePrepare@4
displayName: 'Prepare SonarQube analysis'
inputs:
SonarQube: 'SonarQubeConnection'
scannerMode: 'MSBuild'
projectKey: 'BusinessApp'
projectName: 'Business Application'
- task: DotNetCoreCLI@2
displayName: 'Build for SonarQube'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: SonarQubeAnalyze@4
displayName: 'Run SonarQube analysis'
- task: SonarQubePublish@4
displayName: 'Publish SonarQube results'
inputs:
pollingTimeoutSec: '300'
Release Pipeline
# Release pipeline for Azure App Service
stages:
- stage: Development
displayName: 'Deploy to Development'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs:
- deployment: DeployToDev
displayName: 'Deploy to Development Environment'
environment: 'Development'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
displayName: 'Deploy to Azure App Service'
inputs:
azureSubscription: 'BusinessSubscription'
appType: 'webApp'
appName: 'businessapp-dev'
package: '$(Pipeline.Workspace)/drop/*.zip'
appSettings: |
-ConnectionStrings:DefaultConnection "$(DevConnectionString)"
-AppSettings:Environment "Development"
-AppSettings:ApiKey "$(DevApiKey)"
- task: AzureAppServiceManage@0
displayName: 'Start Azure App Service'
inputs:
azureSubscription: 'BusinessSubscription'
action: 'Start Azure App Service'
webAppName: 'businessapp-dev'
- stage: Staging
displayName: 'Deploy to Staging'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployToStaging
displayName: 'Deploy to Staging Environment'
environment: 'Staging'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
displayName: 'Deploy to Azure App Service'
inputs:
azureSubscription: 'BusinessSubscription'
appType: 'webApp'
appName: 'businessapp-staging'
package: '$(Pipeline.Workspace)/drop/*.zip'
deploymentMethod: 'zipDeploy'
appSettings: |
-ConnectionStrings:DefaultConnection "$(StagingConnectionString)"
-AppSettings:Environment "Staging"
-AppSettings:ApiKey "$(StagingApiKey)"
- task: AzureAppServiceManage@0
displayName: 'Start Azure App Service'
inputs:
azureSubscription: 'BusinessSubscription'
action: 'Start Azure App Service'
webAppName: 'businessapp-staging'
- stage: Production
displayName: 'Deploy to Production'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployToProduction
displayName: 'Deploy to Production Environment'
environment: 'Production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
displayName: 'Deploy to Azure App Service'
inputs:
azureSubscription: 'BusinessSubscription'
appType: 'webApp'
appName: 'businessapp-prod'
package: '$(Pipeline.Workspace)/drop/*.zip'
deploymentMethod: 'zipDeploy'
appSettings: |
-ConnectionStrings:DefaultConnection "$(ProdConnectionString)"
-AppSettings:Environment "Production"
-AppSettings:ApiKey "$(ProdApiKey)"
- task: AzureAppServiceManage@0
displayName: 'Start Azure App Service'
inputs:
azureSubscription: 'BusinessSubscription'
action: 'Start Azure App Service'
webAppName: 'businessapp-prod'
Azure Boards Project Management
Work Item Configuration
# Create work item types
az boards work-item create --title "User Authentication System" --type "Epic" --assigned-to "developer@company.com" --area "BusinessApp" --iteration "Sprint 1"
# Create user stories
az boards work-item create --title "User can login with email and password" --type "User Story" --assigned-to "developer@company.com" --area "BusinessApp\Authentication" --iteration "Sprint 1"
# Create tasks
az boards work-item create --title "Create login API endpoint" --type "Task" --assigned-to "developer@company.com" --area "BusinessApp\Authentication" --iteration "Sprint 1"
# Create bug
az boards work-item create --title "Login button not working on mobile" --type "Bug" --assigned-to "developer@company.com" --area "BusinessApp\Frontend" --iteration "Sprint 1" --priority "High"
Sprint Planning
# Sprint configuration
sprints:
- name: "Sprint 1"
start_date: "2024-01-01"
end_date: "2024-01-14"
capacity: 80
goals:
- "Implement user authentication"
- "Create basic user interface"
- "Set up database schema"
- name: "Sprint 2"
start_date: "2024-01-15"
end_date: "2024-01-28"
capacity: 80
goals:
- "Implement user profile management"
- "Add password reset functionality"
- "Create admin dashboard"
# Team capacity
team_capacity:
- member: "developer@company.com"
capacity_per_day: 6
days_off: ["2024-01-10"]
- member: "tester@company.com"
capacity_per_day: 6
days_off: []
Dashboards and Reports
# Create dashboard
az devops dashboard create --name "Team Dashboard" --description "Main team dashboard"
# Add widgets
az devops dashboard widget add --dashboard-id "team-dashboard" --widget-type "sprint-burndown" --settings '{
"sprintId": "sprint-1",
"title": "Sprint Burndown"
}'
az devops dashboard widget add --dashboard-id "team-dashboard" --widget-type "velocity" --settings '{
"iterations": 6,
"title": "Team Velocity"
}'
# Query work items
az boards query --wiql "SELECT [System.Id], [System.Title], [System.State] FROM WorkItems WHERE [System.TeamProject] = 'BusinessProject' AND [System.WorkItemType] = 'User Story' AND [System.State] = 'Active'"
Azure Test Plans
Test Case Management
# Create test plan
az devops test-plan create --name "Sprint 1 Test Plan" --area-path "BusinessApp" --iteration "Sprint 1"
# Create test suite
az devops test-suite create --plan-id "1" --name "Authentication Tests" --suite-type "StaticTestSuite"
# Create test cases
az devops test-case create --title "Verify user can login with valid credentials" --steps '[
{
"action": "Navigate to login page",
"expected": "Login page displays"
},
{
"action": "Enter valid email and password",
"expected": "Credentials are accepted"
},
{
"action": "Click login button",
"expected": "User is redirected to dashboard"
}
]'
Automated Testing Integration
# Test automation pipeline
stages:
- stage: AutomatedTests
displayName: 'Automated Testing'
jobs:
- job: UnitTests
displayName: 'Unit Tests'
steps:
- task: DotNetCoreCLI@2
displayName: 'Run unit tests'
inputs:
command: 'test'
projects: '**/*Tests*.csproj'
arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage" --logger trx --results-directory $(Agent.TempDirectory)'
- task: PublishTestResults@2
displayName: 'Publish test results'
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
searchFolder: '$(Agent.TempDirectory)'
- job: IntegrationTests
displayName: 'Integration Tests'
steps:
- task: DotNetCoreCLI@2
displayName: 'Run integration tests'
inputs:
command: 'test'
projects: '**/*IntegrationTests*.csproj'
arguments: '--configuration $(buildConfiguration) --logger trx --results-directory $(Agent.TempDirectory)'
- job: UITests
displayName: 'UI Tests'
steps:
- task: DotNetCoreCLI@2
displayName: 'Run UI tests'
inputs:
command: 'test'
projects: '**/*UITests*.csproj'
arguments: '--configuration $(buildConfiguration) --logger trx --results-directory $(Agent.TempDirectory)'
Azure Artifacts
Package Management
# Create artifact feed
az artifacts feed create --name "BusinessPackages" --description "Private NuGet packages for business applications"
# Connect to feed
az artifacts feed permission add --feed-id "BusinessPackages" --role "Contributor" --member "developer@company.com"
# Publish package
dotnet pack --configuration Release --output ./packages
dotnet nuget push ./packages/*.nupkg --source "BusinessPackages" --api-key "API_KEY"
Package Configuration
<!-- NuGet.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="BusinessPackages" value="https://pkgs.dev.azure.com/YourOrganization/BusinessProject/_packaging/BusinessPackages/nuget/v3/index.json" />
</packageSources>
<packageSourceCredentials>
<BusinessPackages>
<add key="Username" value="AzureDevOps" />
<add key="ClearTextPassword" value="PERSONAL_ACCESS_TOKEN" />
</BusinessPackages>
</packageSourceCredentials>
</configuration>
Infrastructure as Code
ARM Template Deployment
# Infrastructure deployment pipeline
stages:
- stage: Infrastructure
displayName: 'Deploy Infrastructure'
jobs:
- job: DeployInfrastructure
displayName: 'Deploy ARM Templates'
steps:
- task: AzureResourceManagerTemplateDeployment@3
displayName: 'Deploy ARM Template'
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: 'BusinessSubscription'
subscriptionId: '$(subscriptionId)'
action: 'Create Or Update Resource Group'
resourceGroupName: 'BusinessApp-RG'
location: 'East US'
templateLocation: 'Linked artifact'
csmFile: '$(Build.SourcesDirectory)/infrastructure/azuredeploy.json'
csmParametersFile: '$(Build.SourcesDirectory)/infrastructure/azuredeploy.parameters.json'
overrideParameters: |
-appName "businessapp-$(environment)"
-sqlServerName "businesssql-$(environment)"
-sqlAdministratorLogin "$(sqlAdminLogin)"
-sqlAdministratorLoginPassword "$(sqlAdminPassword)"
Terraform Integration
# Terraform deployment pipeline
stages:
- stage: Terraform
displayName: 'Terraform Deployment'
jobs:
- job: TerraformPlan
displayName: 'Terraform Plan'
steps:
- task: TerraformInstaller@0
displayName: 'Install Terraform'
inputs:
terraformVersion: '1.0.0'
- task: TerraformTaskV3@3
displayName: 'Terraform Init'
inputs:
provider: 'azurerm'
command: 'init'
workingDirectory: '$(Build.SourcesDirectory)/terraform'
backendServiceArm: 'BusinessSubscription'
backendAzureRmResourceGroupName: 'terraform-state-rg'
backendAzureRmStorageAccountName: 'terraformstate$(environment)'
backendAzureRmContainerName: 'tfstate'
backendAzureRmKey: 'business-app.tfstate'
- task: TerraformTaskV3@3
displayName: 'Terraform Plan'
inputs:
provider: 'azurerm'
command: 'plan'
workingDirectory: '$(Build.SourcesDirectory)/terraform'
environmentServiceNameAzureRM: 'BusinessSubscription'
commandOptions: '-var="environment=$(environment)" -out=tfplan'
- task: TerraformTaskV3@3
displayName: 'Terraform Apply'
inputs:
provider: 'azurerm'
command: 'apply'
workingDirectory: '$(Build.SourcesDirectory)/terraform'
environmentServiceNameAzureRM: 'BusinessSubscription'
commandOptions: 'tfplan'
Security and Compliance
Security Scanning
# Security scanning pipeline
stages:
- stage: Security
displayName: 'Security Scanning'
jobs:
- job: SecurityScan
displayName: 'Run Security Scans'
steps:
- task: CredScan@3
displayName: 'Run Credential Scanner'
inputs:
toolVersion: 'Latest'
scanFolder: '$(Build.SourcesDirectory)'
outputFormat: 'sarif'
debugMode: false
- task: SdtReport@2
displayName: 'Create Security Report'
inputs:
VstsConsole: false
TsvFile: false
HtmlFile: true
AllTools: false
BinSkim: false
BinSkimBreakOn: 'Error'
CredScan: true
CredScanBreakOn: 'Error'
MSRD: false
RoslynAnalyzers: false
RoslynAnalyzersBreakOn: 'Error'
TSLint: false
TSLintBreakOn: 'Error'
ToolLogsNotFoundAction: 'Standard'
- task: PublishSecurityAnalysisLogs@3
displayName: 'Publish Security Analysis Logs'
inputs:
ArtifactName: 'CodeAnalysisLogs'
ArtifactType: 'Container'
AllTools: false
AntiMalware: false
BinSkim: false
CredScan: true
RoslynAnalyzers: false
TSLint: false
ToolLogsNotFoundAction: 'Standard'
Compliance Policies
# Set up compliance policies
az devops security permission update --namespace-id "Security" --token "Organization" --subject "Developers" --allow-bit 1 --deny-bit 0
# Audit logging
az devops audit-log query --start-time "2024-01-01" --end-time "2024-01-31" --filter-by "Project" --filter-value "BusinessProject"
Monitoring and Analytics
Pipeline Analytics
# Pipeline monitoring
stages:
- stage: Monitoring
displayName: 'Pipeline Monitoring'
jobs:
- job: Analytics
displayName: 'Collect Analytics'
steps:
- task: PowerShell@2
displayName: 'Collect Pipeline Metrics'
inputs:
targetType: 'inline'
script: |
$buildId = "$(Build.BuildId)"
$buildResult = "$(Agent.JobStatus)"
$buildDuration = "$(System.TotalJobTime)"
# Send metrics to Application Insights
$body = @{
name = "PipelineExecution"
timestamp = (Get-Date).ToUniversalTime().ToString("o")
data = @{
baseType = "EventData"
baseData = @{
ver = 2
name = "PipelineExecution"
properties = @{
buildId = $buildId
result = $buildResult
duration = $buildDuration
project = "BusinessProject"
}
}
}
} | ConvertTo-Json -Depth 10
$headers = @{
"Content-Type" = "application/json"
}
Invoke-RestMethod -Uri "https://dc.services.visualstudio.com/v2/track" -Method Post -Body $body -Headers $headers
Best Practices
Development Workflow
- Use feature branches for all development work
- Implement pull request reviews for code quality
- Maintain consistent coding standards with linting tools
- Write comprehensive tests for all features
Pipeline Management
- Use YAML pipelines for version control
- Implement proper secret management with Azure Key Vault
- Use pipeline templates for consistency
- Monitor pipeline performance and optimize bottlenecks
Security
- Implement least privilege access for all team members
- Use service connections for Azure resource access
- Enable audit logging for compliance
- Regular security scans in CI/CD pipelines
Project Management
- Define clear work item templates
- Use consistent sprint planning processes
- Implement effective dashboard monitoring
- Regular retrospectives for continuous improvement
Cost Optimization
License Management
# Monitor license usage
az devops user list --output table
az devops security group list --output table
# Optimize license assignment
az devops user update --user "user@company.com" --license-type "stakeholder"
Resource Optimization
# Optimize pipeline agents
pool:
vmImage: 'ubuntu-latest' # Use Linux agents when possible (cheaper)
# Use self-hosted agents for frequent builds
pool:
name: 'SelfHosted'
demands:
- Agent.Name -equals BuildAgent01
Troubleshooting
Common Issues
# Check pipeline logs
az pipelines build show --id "$(Build.BuildId)" --output table
# Debug failed builds
az pipelines build logs download --build-id "$(Build.BuildId)" --output-folder "./logs"
# Test connections
az devops service-endpoint list --output table
az devops service-endpoint show --id "service-endpoint-id"
Performance Optimization
# Optimize build performance
steps:
- task: Cache@2
displayName: 'Cache NuGet packages'
inputs:
key: 'nuget | "$(Agent.OS)" | **/packages.lock.json,!**/bin/**,!**/obj/**'
restoreKeys: |
nuget | "$(Agent.OS)"
nuget
path: '$(NUGET_PACKAGES)'
- task: Cache@2
displayName: 'Cache npm packages'
inputs:
key: 'npm | "$(Agent.OS)" | **/package-lock.json,!**/node_modules/**'
restoreKeys: |
npm | "$(Agent.OS)"
npm
path: '$(npm_config_cache)'
Conclusion
Azure DevOps Services provides a comprehensive platform for modern software development and deployment. Proper implementation of CI/CD pipelines, version control, and project management ensures efficient development workflows and high-quality software delivery.
For professional Azure DevOps implementation and consulting services in Louisville, contact Tyler on Tech Louisville for expert assistance with your DevOps transformation journey.