How to setup self-hosted Azure DevOps agents
Deploy and manage Azure DevOps build agents on your infrastructure
In this guide, I'll walk you through setting up self-hosted Azure DevOps agents across Windows, Linux, and macOS platforms. You'll learn how to configure agent pools, set up DevOps Hub API integrations, and deploy production-ready build infrastructure.
What is an Azure DevOps agent?#
Azure DevOps agents are compute resources that run your build and deployment jobs. While Microsoft-hosted agents are convenient, they come with limitations—shared resources, limited customization, and restricted access to on-premises systems.
Self-hosted agents give you complete control over the build environment. You can install custom software, access private networks, and scale resources based on your specific needs. This is particularly valuable for organizations requiring specialized tooling, enhanced security, or integration with internal systems like the DevOps Hub platform.
Azure DevOps organizes agents into agent pools, which are collections of agents with similar capabilities. When you queue a build, Azure DevOps automatically selects an available agent from the specified pool.
Prerequisites#
Before setting up your self-hosted agents, ensure you have:
- Azure DevOps organization with Project Collection Administrator or Agent Pool Administrator permissions
- Server infrastructure (physical servers, VMs, or cloud instances) running Windows, Linux, or macOS
- Network connectivity to Azure DevOps services (
dev.azure.comand*.dev.azure.com) - DevOps Hub API access with valid bearer token for
https://console.assistance.bg/api/v2/
System requirements#
Minimum specifications per agent:
- CPU: 2+ cores
- RAM: 4GB+ (8GB+ recommended for containerized builds)
- Storage: 50GB+ free space
- Network: Stable internet connection with outbound HTTPS (443) access
Supported operating systems:
- Windows Server 2019/2022, Windows 10/11
- Linux distributions: Ubuntu 20.04+, RHEL 8+, CentOS 8+
- macOS 10.15+ (Intel and Apple Silicon)
Infrastructure setup#
Windows server provisioning#
For Windows-based agents, set up a Windows Server instance with these configurations:
1# Enable Windows Features required for development2Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole3Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux45# Install Chocolatey for package management6Set-ExecutionPolicy Bypass -Scope Process -Force7[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 30728iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))910# Install essential development tools11choco install git nodejs docker-desktop powershell-core -yLinux server provisioning#
For Ubuntu 20.04+ servers:
1#!/bin/bash2# Update system packages3sudo apt update && sudo apt upgrade -y45# Install essential packages6sudo apt install -y curl wget git build-essential software-properties-common78# Install Docker9curl -fsSL https://get.docker.com -o get-docker.sh10sudo sh get-docker.sh11sudo usermod -aG docker $USER1213# Install Node.js14curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -15sudo apt install -y nodejs1617# Install .NET SDK18wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb19sudo dpkg -i packages-microsoft-prod.deb20sudo apt update && sudo apt install -y dotnet-sdk-6.0macOS server provisioning#
For macOS systems:
1# Install Homebrew2/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"34# Install essential development tools5brew install git node docker python3 dotnet67# Install Xcode command line tools8xcode-select --installAgent installation#
Create agent pools#
First, create dedicated agent pools in your Azure DevOps organization:
- Navigate to Organization Settings > Agent pools
- Click Add pool and select Self-hosted
- Create pools for different purposes:
DevOpsHub-Production- Production deploymentsDevOpsHub-Development- Development buildsDevOpsHub-Testing- Test automation
Download and configure agents#
Windows agent setup#
1# Create agent directory2mkdir C:\agent3cd C:\agent45# Download latest agent6Invoke-WebRequest -Uri "https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-win-x64-2.217.2.zip" -OutFile "vsts-agent-win-x64.zip"7Expand-Archive -Path "vsts-agent-win-x64.zip" -DestinationPath "."89# Configure agent (run as Administrator)10.\config.cmd --unattended `11 --url https://dev.azure.com/your-organization `12 --auth pat `13 --token YOUR_PAT_TOKEN `14 --pool "DevOpsHub-Production" `15 --agent "$env:COMPUTERNAME-prod" `16 --runAsService `17 --windowsLogonAccount "NT AUTHORITY\SYSTEM"1819# Install and start service20.\svc.sh install21.\svc.sh startLinux agent setup#
1# Create agent user2sudo useradd -m -s /bin/bash azuredevopsagent3sudo usermod -aG docker azuredevopsagent45# Switch to agent user6sudo su - azuredevopsagent78# Create agent directory9mkdir ~/agent && cd ~/agent1011# Download and extract agent12curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-linux-x64-2.217.2.tar.gz13tar zxvf vsts-agent-linux-x64-2.217.2.tar.gz1415# Install dependencies16sudo ~/agent/bin/installdependencies.sh1718# Configure agent19./config.sh --unattended \20 --url https://dev.azure.com/your-organization \21 --auth pat \22 --token YOUR_PAT_TOKEN \23 --pool "DevOpsHub-Production" \24 --agent "$(hostname)-prod" \25 --acceptTeeEula2627# Install as systemd service28sudo ./svc.sh install azuredevopsagent29sudo ./svc.sh startmacOS agent setup#
1# Create agent directory2mkdir ~/agent && cd ~/agent34# Download agent (choose appropriate architecture)5# For Intel Macs:6curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-osx-x64-2.217.2.tar.gz7# For Apple Silicon Macs:8curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-osx-arm64-2.217.2.tar.gz910# Extract and configure11tar zxvf vsts-agent-osx-*.tar.gz12./config.sh --unattended \13 --url https://dev.azure.com/your-organization \14 --auth pat \15 --token YOUR_PAT_TOKEN \16 --pool "DevOpsHub-Production" \17 --agent "$(hostname)-prod"1819# Install as LaunchDaemon20sudo ./svc.sh install21sudo ./svc.sh startConfiguration#
Agent capabilities#
Configure agent capabilities to ensure jobs run on appropriate infrastructure:
1# Add custom capabilities during configuration2./config.sh --unattended \3 --url https://dev.azure.com/your-organization \4 --auth pat \5 --token YOUR_PAT_TOKEN \6 --pool "DevOpsHub-Production" \7 --agent "$(hostname)-prod" \8 --addcapabilitiesfile capabilities.txt910# capabilities.txt example11DevOpsHub=true12Environment=Production13Docker=20.10.2114Node=18.17.015DotNet=6.0.0Deployment groups#
For deployment scenarios, register agents to deployment groups:
1# Configure deployment group agent2./config.sh --unattended \3 --url https://dev.azure.com/your-organization \4 --auth pat \5 --token YOUR_PAT_TOKEN \6 --deploymentgroup \7 --deploymentgroupname "DevOpsHub-Production" \8 --projectname "YourProject" \9 --agent "$(hostname)-deploy" \10 --addDeploymentGroupTags --deploymentGroupTags "web,api,database"DevOps Hub integration#
Service connections#
Create a service connection for DevOps Hub API access:
- Navigate to Project Settings > Service connections
- Choose Generic REST API service connection
- Configure the connection:
1{2 "name": "DevOpsHub-API",3 "url": "https://console.assistance.bg/api/v2/",4 "authentication": "Token based authentication",5 "token": "YOUR_DEVOPS_HUB_BEARER_TOKEN"6}Pipeline variables#
Set up secure variables for DevOps Hub integration:
1variables:2 - group: DevOpsHub-Variables3 - name: DEVOPS_HUB_API_URL4 value: 'https://console.assistance.bg/api/v2/'5 - name: PROJECT_ID6 value: 'your-project-id'Variable groups#
Create variable groups for different environments:
DevOpsHub-Production:
1DEVOPS_HUB_TOKEN: $(DevOpsHubToken)2ENVIRONMENT: production3API_ENDPOINT: https://console.assistance.bg/api/v2/DevOpsHub-Development:
1DEVOPS_HUB_TOKEN: $(DevOpsHubDevToken)2ENVIRONMENT: development3API_ENDPOINT: https://console.assistance.bg/api/v2/Multi-OS support#
Platform-specific configurations#
Windows-specific setup#
1# azure-pipelines-windows.yml2pool:3 name: 'DevOpsHub-Production'4 demands:5 - Agent.OS -equals Windows_NT6 - DevOpsHub -equals true78variables:9 BuildConfiguration: 'Release'10 BuildPlatform: 'x64'1112steps:13- powershell: |14 Write-Host "Windows Agent: $(Agent.Name)"15 Write-Host "OS Version: $(Agent.OSVersion)"16 displayName: 'Agent Information'1718- task: DotNetCoreCLI@219 inputs:20 command: 'build'21 projects: '**/*.sln'22 arguments: '--configuration $(BuildConfiguration) --verbosity minimal'Linux-specific setup#
1# azure-pipelines-linux.yml2pool:3 name: 'DevOpsHub-Production'4 demands:5 - Agent.OS -equals Linux6 - DevOpsHub -equals true7 - Docker89steps:10- bash: |11 echo "Linux Agent: $(Agent.Name)"12 echo "Distribution: $(lsb_release -d)"13 docker --version14 displayName: 'Agent Information'1516- task: Docker@217 inputs:18 command: 'build'19 Dockerfile: '**/Dockerfile'20 tags: '$(Build.Repository.Name):$(Build.BuildNumber)'macOS-specific setup#
1# azure-pipelines-macos.yml2pool:3 name: 'DevOpsHub-Production'4 demands:5 - Agent.OS -equals Darwin6 - DevOpsHub -equals true78steps:9- bash: |10 echo "macOS Agent: $(Agent.Name)"11 system_profiler SPHardwareDataType | grep "Chip"12 xcodebuild -version13 displayName: 'Agent Information'1415- task: Xcode@516 inputs:17 actions: 'build'18 scheme: 'YourApp'19 sdk: 'iphoneos'20 configuration: 'Release'Architecture support#
ARM and x64 differences#
x64 (Intel/AMD) configuration#
1# Download x64 agent2curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-linux-x64-2.217.2.tar.gz34# Add architecture capability5echo "Architecture=x64" >> capabilities.txt6echo "Platform=Intel" >> capabilities.txtARM64 configuration#
1# Download ARM64 agent (Linux)2curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-linux-arm64-2.217.2.tar.gz34# For macOS Apple Silicon5curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-osx-arm64-2.217.2.tar.gz67# Add architecture capability8echo "Architecture=ARM64" >> capabilities.txt9echo "Platform=AppleSilicon" >> capabilities.txtArchitecture-specific pipelines#
1# Multi-architecture build matrix2strategy:3 matrix:4 x64_linux:5 agentPool: 'DevOpsHub-Production'6 agentDemands: 'Agent.OS -equals Linux, Architecture -equals x64'7 buildArch: 'x64'8 arm64_linux:9 agentPool: 'DevOpsHub-Production'10 agentDemands: 'Agent.OS -equals Linux, Architecture -equals ARM64'11 buildArch: 'arm64'12 x64_windows:13 agentPool: 'DevOpsHub-Production'14 agentDemands: 'Agent.OS -equals Windows_NT, Architecture -equals x64'15 buildArch: 'x64'1617pool:18 name: $(agentPool)19 demands: $(agentDemands)Test pipeline#
Here's a comprehensive test pipeline that demonstrates DevOps Hub integration:
1# azure-pipelines-devopshub-test.yml2name: DevOpsHub Integration Test34trigger:5 branches:6 include:7 - main8 - develop910pool:11 name: 'DevOpsHub-Production'12 demands:13 - DevOpsHub -equals true1415variables:16 - group: DevOpsHub-Variables1718stages:19- stage: Setup20 jobs:21 - job: CreateBranch22 steps:23 - task: PowerShell@224 displayName: 'Create DevOps Hub Branch'25 inputs:26 targetType: 'inline'27 script: |28 $headers = @{29 'Authorization' = 'Bearer $(DEVOPS_HUB_TOKEN)'30 'Content-Type' = 'application/json'31 }3233 $body = @{34 branch = @{35 name = "azure-pipeline-$(Build.BuildNumber)"36 parent_branch = "main"37 }38 } | ConvertTo-Json -Depth 33940 $response = Invoke-RestMethod -Uri "$(DEVOPS_HUB_API_URL)/projects/$(PROJECT_ID)/branches" -Method POST -Headers $headers -Body $body41 Write-Host "Created branch: $($response.branch.name)"42 Write-Host "##vso[task.setvariable variable=BRANCH_ID;isOutput=true]$($response.branch.id)"4344- stage: Build45 dependsOn: Setup46 jobs:47 - job: BuildApplication48 variables:49 BRANCH_ID: $[ stageDependencies.Setup.CreateBranch.outputs['CreateBranch.BRANCH_ID'] ]50 steps:51 - checkout: self52 persistCredentials: true5354 - task: RestCallout@155 displayName: 'Update Branch Status'56 inputs:57 connectionType: 'serviceEndpoint'58 serviceConnection: 'DevOpsHub-API'59 method: 'PATCH'60 url: '$(DEVOPS_HUB_API_URL)/projects/$(PROJECT_ID)/branches/$(BRANCH_ID)'61 body: |62 {63 "status": "building",64 "metadata": {65 "azure_build_id": "$(Build.BuildId)",66 "azure_build_number": "$(Build.BuildNumber)"67 }68 }6970 - task: DotNetCoreCLI@271 displayName: 'Build Application'72 inputs:73 command: 'build'74 projects: '**/*.csproj'75 arguments: '--configuration Release'7677 - task: DotNetCoreCLI@278 displayName: 'Run Tests'79 inputs:80 command: 'test'81 projects: '**/*Tests.csproj'82 arguments: '--configuration Release --collect:"XPlat Code Coverage"'8384- stage: Deploy85 dependsOn:86 - Setup87 - Build88 condition: succeeded()89 jobs:90 - deployment: DeployToDevOpsHub91 environment: 'DevOpsHub-Production'92 variables:93 BRANCH_ID: $[ stageDependencies.Setup.CreateBranch.outputs['CreateBranch.BRANCH_ID'] ]94 strategy:95 runOnce:96 deploy:97 steps:98 - task: AzureCLI@299 displayName: 'Deploy to Environment'100 inputs:101 azureSubscription: 'AzureServiceConnection'102 scriptType: 'bash'103 scriptLocation: 'inlineScript'104 inlineScript: |105 echo "Deploying application..."106 # Your deployment commands here107108 - task: RestCallout@1109 displayName: 'Mark Deployment Complete'110 inputs:111 connectionType: 'serviceEndpoint'112 serviceConnection: 'DevOpsHub-API'113 method: 'POST'114 url: '$(DEVOPS_HUB_API_URL)/projects/$(PROJECT_ID)/deployments'115 body: |116 {117 "branch_id": "$(BRANCH_ID)",118 "environment": "production",119 "status": "success",120 "deployed_at": "$(System.DefaultWorkingDirectory)",121 "metadata": {122 "azure_build_id": "$(Build.BuildId)",123 "azure_release_id": "$(Release.ReleaseId)"124 }125 }126127- stage: Cleanup128 dependsOn:129 - Setup130 - Deploy131 condition: always()132 jobs:133 - job: CleanupResources134 variables:135 BRANCH_ID: $[ stageDependencies.Setup.CreateBranch.outputs['CreateBranch.BRANCH_ID'] ]136 steps:137 - task: RestCallout@1138 displayName: 'Archive Branch'139 condition: succeeded()140 inputs:141 connectionType: 'serviceEndpoint'142 serviceConnection: 'DevOpsHub-API'143 method: 'DELETE'144 url: '$(DEVOPS_HUB_API_URL)/projects/$(PROJECT_ID)/branches/$(BRANCH_ID)'Production deployment#
Service setup and monitoring#
Windows service configuration#
1# Enhanced service configuration2$serviceName = "vstsagent.your-organization.DevOpsHub-Production.$env:COMPUTERNAME-prod"34# Configure service for automatic recovery5sc.exe failure $serviceName reset= 86400 actions= restart/60000/restart/60000/restart/6000067# Set service to start automatically with delayed start8sc.exe config $serviceName start= delayed-auto910# Configure service to run with specific user account11sc.exe config $serviceName obj= ".\azuredevopsagent" password= "YourSecurePassword"Linux systemd service#
1# Create enhanced systemd service2sudo tee /etc/systemd/system/azuredevops-agent.service > /dev/null <<EOF3[Unit]4Description=Azure DevOps Agent5After=network.target6Wants=network.target78[Service]9Type=simple10User=azuredevopsagent11WorkingDirectory=/home/azuredevopsagent/agent12ExecStart=/home/azuredevopsagent/agent/runsvc.sh13Restart=always14RestartSec=3015KillMode=process16KillSignal=SIGINT17TimeoutStopSec=5min1819# Security hardening20NoNewPrivileges=yes21PrivateTmp=yes22ProtectHome=yes23ProtectSystem=strict24ReadWritePaths=/home/azuredevopsagent2526[Install]27WantedBy=multi-user.target28EOF2930sudo systemctl daemon-reload31sudo systemctl enable azuredevops-agent32sudo systemctl start azuredevops-agentScaling and parallel jobs#
Horizontal scaling setup#
1#!/bin/bash2# Scale agents horizontally3AGENT_COUNT=44BASE_NAME="devopshub-agent"56for i in $(seq 1 $AGENT_COUNT); do7 AGENT_DIR="/home/azuredevopsagent/agent-$i"8 mkdir -p $AGENT_DIR9 cd $AGENT_DIR1011 # Download and configure each agent12 tar zxvf /tmp/vsts-agent-linux-x64.tar.gz13 ./config.sh --unattended \14 --url https://dev.azure.com/your-organization \15 --auth pat \16 --token $PAT_TOKEN \17 --pool "DevOpsHub-Production" \18 --agent "$BASE_NAME-$i" \19 --work "_work-$i"2021 # Install as separate service22 sudo ./svc.sh install azuredevopsagent-$i23 sudo ./svc.sh start azuredevopsagent-$i24doneAgent pool management#
1# Dynamic agent allocation pipeline2pool:3 name: 'DevOpsHub-Production'45strategy:6 parallel: 4 # Run 4 jobs in parallel78jobs:9- job: ParallelJob10 timeoutInMinutes: 6011 steps:12 - script: |13 echo "Running on agent: $(Agent.Name)"14 echo "Job attempt: $(System.JobAttempt)"15 echo "Parallel execution: $(System.JobPositionInPhase) of $(System.TotalJobsInPhase)"Monitoring and alerts#
Agent health monitoring#
1#!/bin/bash2# Agent health check script3AGENT_STATUS=$(systemctl is-active azuredevops-agent)4API_URL="https://console.assistance.bg/api/v2/monitoring/agents"56if [ "$AGENT_STATUS" != "active" ]; then7 # Report agent failure to DevOps Hub8 curl -X POST "$API_URL" \9 -H "Authorization: Bearer $DEVOPS_HUB_TOKEN" \10 -H "Content-Type: application/json" \11 -d '{12 "agent_name": "'$(hostname)'",13 "status": "failed",14 "timestamp": "'$(date -Iseconds)'",15 "error": "Service not active"16 }'1718 # Restart service19 sudo systemctl restart azuredevops-agent20fiLog monitoring#
1# Windows PowerShell monitoring script2$LogPath = "C:\agent\_diag\Agent_*.log"3$AlertKeywords = @("ERROR", "FATAL", "Exception")45Get-Content $LogPath -Tail 100 | ForEach-Object {6 foreach ($keyword in $AlertKeywords) {7 if ($_ -like "*$keyword*") {8 # Send alert to DevOps Hub9 $alertData = @{10 agent_name = $env:COMPUTERNAME11 level = "error"12 message = $_13 timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ")14 } | ConvertTo-Json1516 Invoke-RestMethod -Uri "https://console.assistance.bg/api/v2/monitoring/alerts" `17 -Method POST `18 -Headers @{ "Authorization" = "Bearer $env:DEVOPS_HUB_TOKEN" } `19 -Body $alertData `20 -ContentType "application/json"21 }22 }23}Security hardening#
Agent isolation#
1# Create isolated agent user with restricted permissions2sudo useradd -m -s /bin/bash -G docker azuredevopsagent3sudo passwd -l azuredevopsagent # Lock password authentication45# Configure sudo permissions6echo "azuredevopsagent ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/systemctl restart azuredevops-agent" | sudo tee /etc/sudoers.d/azuredevops-agent78# Set up directory permissions9sudo chown -R azuredevopsagent:azuredevopsagent /home/azuredevopsagent10sudo chmod 750 /home/azuredevopsagentNetwork security#
1# Configure firewall rules2sudo ufw enable3sudo ufw allow out 443 # HTTPS to Azure DevOps4sudo ufw allow out 80 # HTTP redirects5sudo ufw deny in 22 # Deny incoming SSH6sudo ufw deny in 80 # Deny incoming HTTP7sudo ufw deny in 443 # Deny incoming HTTPSToken security#
1# Store PAT token securely2echo "YOUR_PAT_TOKEN" | sudo tee /etc/azuredevops-agent-token3sudo chown azuredevopsagent:azuredevopsagent /etc/azuredevops-agent-token4sudo chmod 600 /etc/azuredevops-agent-token56# Reference in agent configuration7PAT_TOKEN=$(sudo cat /etc/azuredevops-agent-token)This comprehensive guide provides everything needed to deploy production-ready Azure DevOps self-hosted agents with full DevOps Hub integration. The configuration supports multi-platform deployments, security hardening, and enterprise-scale monitoring.