Azure Managed DevOps Pool is a new feature from Microsoft that provides a fully managed DevOps pool, eliminating the need for Azure Virtual Machine Scale Sets for DevOps agents. With this solution, you can create an agent pool within your virtual network. At a similar price point, you gain significantly more functionality. One of the most appealing aspects is the ability to leverage Microsoft-hosted runner images, so you don’t have to build a custom image with all the necessary tools. However, if you need a custom image to include additional software, check out my article on Azure DevOps self-hosted agents using Packer and VMSS for a step-by-step guide.
In the current scenario, Azure Managed DevOps Pool is the optimal choice for hosting agents within your Azure Virtual Network, providing a secure and fully managed environment for your CI/CD processes.
Status of the Azure Managed DevOps Pool deployment & test pipeline
Prerequisites
Before you can fully leverage Azure Managed DevOps Pool in your Azure environment, you must complete a few essential setup steps. By taking care of these prerequisites – registering the correct resource providers, adjusting resource quotas, and assigning the necessary permissions – you’ll ensure a seamless configuration and be ready to take full advantage of Azure’s robust DevOps capabilities.
- Register Resource Providers
- In your Azure subscription, go to Resource Providers in the left-hand menu.
- Register the following providers:
- Microsoft.DevOpsInfrastructure
- Microsoft.DevCenter
- Adjust Resource Quotas
- In the Azure portal, search for Quotas.
- Select the Managed DevOps Pool tile.
- Locate the Standard DADSv5 VM family; each agent in the pool uses 2 vCPUs.
- Increase (or decrease) the quota to accommodate your project’s needs.
- Assign Permissions to Create Azure DevOps Agent Pools
- In your Azure DevOps organization, navigate to Organization Settings.
- Select Agent Pools, then click Security in the upper-right corner.
- Locate your service principal and assign the Administrator role, then Save.
- Repeat this process at both the Organization and Project levels.
- Make sure your Azure DevOps organization and service principal are in the same Azure Entra ID (Azure AD) tenant.
By completing these steps, you’ll be prepared to configure and use Azure Managed DevOps Pool effectively, ensuring a secure, reliable, and high-performing environment for your CI/CD pipelines.
Azure Managed DevOps Pool Terraform: Complete Code
If you’re looking for a fully functional Terraform configuration to create an Azure DevOps Managed Pool, you can find the complete code in my GitHub repository. If you’re already familiar with configuring Terraform providers and managing Infrastructure as Code, feel free to skip the rest of this article and download the source code directly.
I hope you find it helpful – if you do, I’d greatly appreciate a comment or reaction on my LinkedIn. Your feedback not only helps improve the code but also fosters community collaboration and learning!
Azure Managed DevOps Pool Terraform configuration
In this solution, a main.auto.tfvars file is used to store variable values. Here’s an example of the values:
spn-client-id = "PRINCIPAL_CLIENT_ID"
spn-tenant-id = "TENANT_ID"
azure_devops_organization_name = "ADO_ORGANIZATION"
subscription-id = "SUBSCRIPTION_ID"
ado_project_id = "ADO_PROJECT_ID"
pipeline_ids = ["PIPELINE_ID"]
maximum_concurrency = 4
Obtaining Required IDs and Tokens
- Azure DevOps Project ID
- You can retrieve this using a cURL command and your Personal Access Token (PAT) from Azure DevOps.
AUTH_HEADER=$(echo -n ":PAT_TOKEN" | base64)
curl -v -X GET \
-H "Content-Type: application/json" \
-H "Authorization: Basic $AUTH_HEADER" \
-H "Accept: application/json" \
"https://dev.azure.com/$ORGANIZATION/_apis/projects?api-version=7.1"
- Replace PAT_TOKEN with your Personal Access Token and $ORGANIZATION with your Azure DevOps organization name.
- Pipeline ID
- You can find the Pipeline ID by looking at the URL of the pipeline in Azure DevOps.
- For example, if the URL is:
https://dev.azure.com/azure-way/basic-pipelines/_build?definitionId=1
The Pipeline ID is 1.
Applying Terraform with Client Secret
When running Terraform apply, you can pass in your service principal’s client secret like so:
terraform apply -var="spn-client-secret=SPN_CLIENT_SECRET"
Default Agent Settings
By default, the setup creates a maximum of 4 agents (maximum_concurrency = 4
). Adjust this value to suit your organization’s needs for parallel builds.
Azure Managed DevOps Pool: Automated Testing
With both Terraform and Azure evolving rapidly, it’s important to make sure your solutions continue to function seamlessly amid ongoing updates. To achieve this, I created automated tests that verify the entire Azure Managed DevOps Pool setup. These tests do the following:
- Create a new Azure Managed DevOps Pool
- Add a new agent pool at both the project and organization levels
- Run a pipeline within Azure DevOps to confirm that everything is configured correctly
- The test checks the result of the ADO pipeline, if the status is successful then I assume that the agent pool is working.
Leveraging Multiple Images in Your Pipeline
One of the coolest features of Azure Managed DevOps Pool is the ability to specify different images on a per-job basis. In my automated test pipeline, each job requests a different image, such as ubuntu-20.04, ubuntu-22.04, windows-2019, or windows-2022. The pipeline you can find below:
trigger:
- main
jobs:
- job: Ubuntu_22_04
displayName: 'Run on Ubuntu 22.04'
pool:
name: azure-way-mdp # Name of Managed DevOps Pool
demands:
- ImageOverride -equals ubuntu-22.04
steps:
- script: |
echo "Running on Ubuntu 22.04"
uname -a
displayName: 'Run Bash script on Ubuntu 22.04'
- job: Ubuntu_20_04
displayName: 'Run on Ubuntu 20.04'
pool:
name: azure-way-mdp # Name of Managed DevOps Pool
demands:
- ImageOverride -equals ubuntu-20.04
steps:
- script: |
echo "Running on Ubuntu 20.04"
uname -a
displayName: 'Run Bash script on Ubuntu 20.04'
- job: Windows_2019
displayName: 'Run on Windows 2019'
pool:
name: azure-way-mdp # Name of Managed DevOps Pool
demands:
- ImageOverride -equals windows-2019
steps:
- powershell: |
Write-Host "Running on Windows 2019"
Get-ComputerInfo
displayName: 'Run PowerShell script on Windows 2019'
- job: Windows_2022
displayName: 'Run on Windows 2022'
pool:
name: azure-way-mdp # Name of Managed DevOps Pool
demands:
- ImageOverride -equals windows-2022
steps:
- powershell: |
Write-Host "Running on Windows 2022"
Get-ComputerInfo
displayName: 'Run PowerShell script on Windows 2022'
The repository is publicly available and can be accessed here.
Azure Managed DevOps Pool – Terraform setup
The first step in setting up your Azure Managed DevOps Pool with Terraform is to configure the necessary providers:
- Azure RM Provider – Enables you to manage Azure resources.
- Azure DevOps Provider – Allows you to configure Azure DevOps resources directly from Terraform.
provider "azuredevops" {
org_service_url = local.azure_devops_organization_url
client_id = var.spn-client-id
client_secret = var.spn-client-secret
tenant_id = var.spn-tenant-id
}
provider "azurerm" {
features {}
subscription_id = var.subscription-id
client_id = var.spn-client-id
client_secret = var.spn-client-secret
tenant_id = var.spn-tenant-id
}
In my setup, I’m using the same account for both Azure RM and Azure DevOps providers, but you’re free to use separate accounts if that suits your environment better.
The next step is to assign the required permissions to the built-in service principal responsible for configuring your networking. This principal requires at least the following permissions to function correctly:
- Microsoft.Network/virtualNetworks/subnets/join/action
- Microsoft.Network/virtualNetworks/subnets/serviceAssociationLinks/validate/action
- Microsoft.Network/virtualNetworks/subnets/serviceAssociationLinks/write
- Microsoft.Network/virtualNetworks/subnets/serviceAssociationLinks/delete
- Reader
You obtain built-in service principal by using data:
data "azuread_service_principal" "this" {
display_name = "DevOpsInfrastructure"
}
For the Azure Managed DevOps Pool to function correctly within your virtual network, you must delegate the subnet to the appropriate resource type. Specifically, delegate your subnet to:
resource "azurerm_subnet" "this" {
name = "managed-pool-subnet"
resource_group_name = azurerm_resource_group.this.name
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = var.subnet_address_prefixes
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.DevOpsInfrastructure/pools"
actions = [
"Microsoft.Network/virtualNetworks/subnets/join/action",
]
}
}
}
To properly configure your Azure Managed DevOps Pool, you must assign both the custom roles (for networking) and the Reader role to the pre-built service principal.
resource "azurerm_role_assignment" "reader" {
principal_id = data.azuread_service_principal.this.object_id
role_definition_name = "Reader"
scope = azurerm_virtual_network.this.id
}
resource "azurerm_role_assignment" "subnet_join" {
principal_id = data.azuread_service_principal.this.object_id
role_definition_id = azurerm_role_definition.this.role_definition_resource_id
scope = azurerm_virtual_network.this.id
}
To ensure your pipelines can use the newly created Azure DevOps Managed Pool, you need to perform two additional steps:
- Authorize the Agent Pool at the Project Level
Grant your pipeline explicit permission to access the new pool. - Create an Agent Queue
In Azure DevOps, create an agent queue linked to the new pool so that your builds and releases can run seamlessly.
data "azuredevops_agent_queue" "this" {
project_id = var.ado_project_id
name = module.managed_devops_pool.name
depends_on = [module.managed_devops_pool]
}
data "azuredevops_build_definition" "example" {
project_id = var.ado_project_id
name = var.azure_devops_build_definition_name
}
resource "azuredevops_pipeline_authorization" "this" {
for_each = toset(var.pipeline_ids)
project_id = var.ado_project_id
resource_id = data.azuredevops_agent_queue.this.id
type = "queue"
pipeline_id = each.key
}
Once you’ve registered all providers, delegated your subnet, and assigned the necessary roles to the service principal, you can proceed with the final step: creating your Azure Managed DevOps Pool
module "managed_devops_pool" {
source = "Azure/avm-res-devopsinfrastructure-pool/azurerm"
version = "0.2.2"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
name = "azure-way-mdp"
dev_center_project_resource_id = azurerm_dev_center_project.this.id
subnet_id = azurerm_subnet.this.id
maximum_concurrency = var.maximum_concurrency
organization_profile = {
organizations = [{
name = var.azure_devops_organization_name
projects = var.azure_devops_project_names
}]
}
fabric_profile_images = [
{
well_known_image_name = "ubuntu-20.04/latest"
aliases = [
"ubuntu-20.04/latest",
"ubuntu-20.04"
]
},
{
well_known_image_name = "ubuntu-22.04/latest"
aliases = [
"ubuntu-22.04/latest",
"ubuntu-22.04"
]
},
{
well_known_image_name = "ubuntu-24.04/latest"
aliases = [
"ubuntu-24.04/latest",
"ubuntu-24.04"
]
},
{
well_known_image_name = "windows-2019/latest"
aliases = [
"windows-2019/latest",
"windows-2019"
]
},
{
well_known_image_name = "windows-2022/latest"
aliases = [
"windows-2022/latest",
"windows-2022"
]
}
]
enable_telemetry = var.enable_telemetry
depends_on = [time_sleep.wait_1_minute]
}
Any images specified in the well_known_image_name
property become immediately available to your pipelines. For a fully functional example, check out the complete code here.
Summary
Azure Managed DevOps Pool is Microsoft’s latest fully managed solution for hosting Azure DevOps build agents securely in your Azure Virtual Network. By using Terraform and leveraging Microsoft-hosted runner images (or custom images if needed), you can simplify your CI/CD pipelines without maintaining Azure Virtual Machine Scale Sets. To see the full Terraform setup, visit the linked GitHub repository – and don’t forget to follow me on LinkedIn and share your comments on my posts to help foster community collaboration and learning!