Runners

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.com and *.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 development
2
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole
3
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
4
5
# Install Chocolatey for package management
6
Set-ExecutionPolicy Bypass -Scope Process -Force
7
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
8
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
9
10
# Install essential development tools
11
choco install git nodejs docker-desktop powershell-core -y

Linux server provisioning#

For Ubuntu 20.04+ servers:

1
#!/bin/bash
2
# Update system packages
3
sudo apt update && sudo apt upgrade -y
4
5
# Install essential packages
6
sudo apt install -y curl wget git build-essential software-properties-common
7
8
# Install Docker
9
curl -fsSL https://get.docker.com -o get-docker.sh
10
sudo sh get-docker.sh
11
sudo usermod -aG docker $USER
12
13
# Install Node.js
14
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
15
sudo apt install -y nodejs
16
17
# Install .NET SDK
18
wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
19
sudo dpkg -i packages-microsoft-prod.deb
20
sudo apt update && sudo apt install -y dotnet-sdk-6.0

macOS server provisioning#

For macOS systems:

1
# Install Homebrew
2
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
3
4
# Install essential development tools
5
brew install git node docker python3 dotnet
6
7
# Install Xcode command line tools
8
xcode-select --install

Agent installation#

Create agent pools#

First, create dedicated agent pools in your Azure DevOps organization:

  1. Navigate to Organization Settings > Agent pools
  2. Click Add pool and select Self-hosted
  3. Create pools for different purposes:
    • DevOpsHub-Production - Production deployments
    • DevOpsHub-Development - Development builds
    • DevOpsHub-Testing - Test automation

Download and configure agents#

Windows agent setup#

1
# Create agent directory
2
mkdir C:\agent
3
cd C:\agent
4
5
# Download latest agent
6
Invoke-WebRequest -Uri "https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-win-x64-2.217.2.zip" -OutFile "vsts-agent-win-x64.zip"
7
Expand-Archive -Path "vsts-agent-win-x64.zip" -DestinationPath "."
8
9
# 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"
18
19
# Install and start service
20
.\svc.sh install
21
.\svc.sh start

Linux agent setup#

1
# Create agent user
2
sudo useradd -m -s /bin/bash azuredevopsagent
3
sudo usermod -aG docker azuredevopsagent
4
5
# Switch to agent user
6
sudo su - azuredevopsagent
7
8
# Create agent directory
9
mkdir ~/agent && cd ~/agent
10
11
# Download and extract agent
12
curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-linux-x64-2.217.2.tar.gz
13
tar zxvf vsts-agent-linux-x64-2.217.2.tar.gz
14
15
# Install dependencies
16
sudo ~/agent/bin/installdependencies.sh
17
18
# Configure agent
19
./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
--acceptTeeEula
26
27
# Install as systemd service
28
sudo ./svc.sh install azuredevopsagent
29
sudo ./svc.sh start

macOS agent setup#

1
# Create agent directory
2
mkdir ~/agent && cd ~/agent
3
4
# Download agent (choose appropriate architecture)
5
# For Intel Macs:
6
curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-osx-x64-2.217.2.tar.gz
7
# For Apple Silicon Macs:
8
curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-osx-arm64-2.217.2.tar.gz
9
10
# Extract and configure
11
tar zxvf vsts-agent-osx-*.tar.gz
12
./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"
18
19
# Install as LaunchDaemon
20
sudo ./svc.sh install
21
sudo ./svc.sh start

Configuration#

Agent capabilities#

Configure agent capabilities to ensure jobs run on appropriate infrastructure:

1
# Add custom capabilities during configuration
2
./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.txt
9
10
# capabilities.txt example
11
DevOpsHub=true
12
Environment=Production
13
Docker=20.10.21
14
Node=18.17.0
15
DotNet=6.0.0

Deployment groups#

For deployment scenarios, register agents to deployment groups:

1
# Configure deployment group agent
2
./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:

  1. Navigate to Project Settings > Service connections
  2. Choose Generic REST API service connection
  3. 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:

1
variables:
2
- group: DevOpsHub-Variables
3
- name: DEVOPS_HUB_API_URL
4
value: 'https://console.assistance.bg/api/v2/'
5
- name: PROJECT_ID
6
value: 'your-project-id'

Variable groups#

Create variable groups for different environments:

DevOpsHub-Production:

1
DEVOPS_HUB_TOKEN: $(DevOpsHubToken)
2
ENVIRONMENT: production
3
API_ENDPOINT: https://console.assistance.bg/api/v2/

DevOpsHub-Development:

1
DEVOPS_HUB_TOKEN: $(DevOpsHubDevToken)
2
ENVIRONMENT: development
3
API_ENDPOINT: https://console.assistance.bg/api/v2/

Multi-OS support#

Platform-specific configurations#

Windows-specific setup#

1
# azure-pipelines-windows.yml
2
pool:
3
name: 'DevOpsHub-Production'
4
demands:
5
- Agent.OS -equals Windows_NT
6
- DevOpsHub -equals true
7
8
variables:
9
BuildConfiguration: 'Release'
10
BuildPlatform: 'x64'
11
12
steps:
13
- powershell: |
14
Write-Host "Windows Agent: $(Agent.Name)"
15
Write-Host "OS Version: $(Agent.OSVersion)"
16
displayName: 'Agent Information'
17
18
- task: DotNetCoreCLI@2
19
inputs:
20
command: 'build'
21
projects: '**/*.sln'
22
arguments: '--configuration $(BuildConfiguration) --verbosity minimal'

Linux-specific setup#

1
# azure-pipelines-linux.yml
2
pool:
3
name: 'DevOpsHub-Production'
4
demands:
5
- Agent.OS -equals Linux
6
- DevOpsHub -equals true
7
- Docker
8
9
steps:
10
- bash: |
11
echo "Linux Agent: $(Agent.Name)"
12
echo "Distribution: $(lsb_release -d)"
13
docker --version
14
displayName: 'Agent Information'
15
16
- task: Docker@2
17
inputs:
18
command: 'build'
19
Dockerfile: '**/Dockerfile'
20
tags: '$(Build.Repository.Name):$(Build.BuildNumber)'

macOS-specific setup#

1
# azure-pipelines-macos.yml
2
pool:
3
name: 'DevOpsHub-Production'
4
demands:
5
- Agent.OS -equals Darwin
6
- DevOpsHub -equals true
7
8
steps:
9
- bash: |
10
echo "macOS Agent: $(Agent.Name)"
11
system_profiler SPHardwareDataType | grep "Chip"
12
xcodebuild -version
13
displayName: 'Agent Information'
14
15
- task: Xcode@5
16
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 agent
2
curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-linux-x64-2.217.2.tar.gz
3
4
# Add architecture capability
5
echo "Architecture=x64" >> capabilities.txt
6
echo "Platform=Intel" >> capabilities.txt

ARM64 configuration#

1
# Download ARM64 agent (Linux)
2
curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-linux-arm64-2.217.2.tar.gz
3
4
# For macOS Apple Silicon
5
curl -O https://vstsagentpackage.azureedge.net/agent/2.217.2/vsts-agent-osx-arm64-2.217.2.tar.gz
6
7
# Add architecture capability
8
echo "Architecture=ARM64" >> capabilities.txt
9
echo "Platform=AppleSilicon" >> capabilities.txt

Architecture-specific pipelines#

1
# Multi-architecture build matrix
2
strategy:
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'
16
17
pool:
18
name: $(agentPool)
19
demands: $(agentDemands)

Test pipeline#

Here's a comprehensive test pipeline that demonstrates DevOps Hub integration:

1
# azure-pipelines-devopshub-test.yml
2
name: DevOpsHub Integration Test
3
4
trigger:
5
branches:
6
include:
7
- main
8
- develop
9
10
pool:
11
name: 'DevOpsHub-Production'
12
demands:
13
- DevOpsHub -equals true
14
15
variables:
16
- group: DevOpsHub-Variables
17
18
stages:
19
- stage: Setup
20
jobs:
21
- job: CreateBranch
22
steps:
23
- task: PowerShell@2
24
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
}
32
33
$body = @{
34
branch = @{
35
name = "azure-pipeline-$(Build.BuildNumber)"
36
parent_branch = "main"
37
}
38
} | ConvertTo-Json -Depth 3
39
40
$response = Invoke-RestMethod -Uri "$(DEVOPS_HUB_API_URL)/projects/$(PROJECT_ID)/branches" -Method POST -Headers $headers -Body $body
41
Write-Host "Created branch: $($response.branch.name)"
42
Write-Host "##vso[task.setvariable variable=BRANCH_ID;isOutput=true]$($response.branch.id)"
43
44
- stage: Build
45
dependsOn: Setup
46
jobs:
47
- job: BuildApplication
48
variables:
49
BRANCH_ID: $[ stageDependencies.Setup.CreateBranch.outputs['CreateBranch.BRANCH_ID'] ]
50
steps:
51
- checkout: self
52
persistCredentials: true
53
54
- task: RestCallout@1
55
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
}
69
70
- task: DotNetCoreCLI@2
71
displayName: 'Build Application'
72
inputs:
73
command: 'build'
74
projects: '**/*.csproj'
75
arguments: '--configuration Release'
76
77
- task: DotNetCoreCLI@2
78
displayName: 'Run Tests'
79
inputs:
80
command: 'test'
81
projects: '**/*Tests.csproj'
82
arguments: '--configuration Release --collect:"XPlat Code Coverage"'
83
84
- stage: Deploy
85
dependsOn:
86
- Setup
87
- Build
88
condition: succeeded()
89
jobs:
90
- deployment: DeployToDevOpsHub
91
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@2
99
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 here
107
108
- task: RestCallout@1
109
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
}
126
127
- stage: Cleanup
128
dependsOn:
129
- Setup
130
- Deploy
131
condition: always()
132
jobs:
133
- job: CleanupResources
134
variables:
135
BRANCH_ID: $[ stageDependencies.Setup.CreateBranch.outputs['CreateBranch.BRANCH_ID'] ]
136
steps:
137
- task: RestCallout@1
138
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 configuration
2
$serviceName = "vstsagent.your-organization.DevOpsHub-Production.$env:COMPUTERNAME-prod"
3
4
# Configure service for automatic recovery
5
sc.exe failure $serviceName reset= 86400 actions= restart/60000/restart/60000/restart/60000
6
7
# Set service to start automatically with delayed start
8
sc.exe config $serviceName start= delayed-auto
9
10
# Configure service to run with specific user account
11
sc.exe config $serviceName obj= ".\azuredevopsagent" password= "YourSecurePassword"

Linux systemd service#

1
# Create enhanced systemd service
2
sudo tee /etc/systemd/system/azuredevops-agent.service > /dev/null <<EOF
3
[Unit]
4
Description=Azure DevOps Agent
5
After=network.target
6
Wants=network.target
7
8
[Service]
9
Type=simple
10
User=azuredevopsagent
11
WorkingDirectory=/home/azuredevopsagent/agent
12
ExecStart=/home/azuredevopsagent/agent/runsvc.sh
13
Restart=always
14
RestartSec=30
15
KillMode=process
16
KillSignal=SIGINT
17
TimeoutStopSec=5min
18
19
# Security hardening
20
NoNewPrivileges=yes
21
PrivateTmp=yes
22
ProtectHome=yes
23
ProtectSystem=strict
24
ReadWritePaths=/home/azuredevopsagent
25
26
[Install]
27
WantedBy=multi-user.target
28
EOF
29
30
sudo systemctl daemon-reload
31
sudo systemctl enable azuredevops-agent
32
sudo systemctl start azuredevops-agent

Scaling and parallel jobs#

Horizontal scaling setup#

1
#!/bin/bash
2
# Scale agents horizontally
3
AGENT_COUNT=4
4
BASE_NAME="devopshub-agent"
5
6
for i in $(seq 1 $AGENT_COUNT); do
7
AGENT_DIR="/home/azuredevopsagent/agent-$i"
8
mkdir -p $AGENT_DIR
9
cd $AGENT_DIR
10
11
# Download and configure each agent
12
tar zxvf /tmp/vsts-agent-linux-x64.tar.gz
13
./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"
20
21
# Install as separate service
22
sudo ./svc.sh install azuredevopsagent-$i
23
sudo ./svc.sh start azuredevopsagent-$i
24
done

Agent pool management#

1
# Dynamic agent allocation pipeline
2
pool:
3
name: 'DevOpsHub-Production'
4
5
strategy:
6
parallel: 4 # Run 4 jobs in parallel
7
8
jobs:
9
- job: ParallelJob
10
timeoutInMinutes: 60
11
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/bash
2
# Agent health check script
3
AGENT_STATUS=$(systemctl is-active azuredevops-agent)
4
API_URL="https://console.assistance.bg/api/v2/monitoring/agents"
5
6
if [ "$AGENT_STATUS" != "active" ]; then
7
# Report agent failure to DevOps Hub
8
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
}'
17
18
# Restart service
19
sudo systemctl restart azuredevops-agent
20
fi

Log monitoring#

1
# Windows PowerShell monitoring script
2
$LogPath = "C:\agent\_diag\Agent_*.log"
3
$AlertKeywords = @("ERROR", "FATAL", "Exception")
4
5
Get-Content $LogPath -Tail 100 | ForEach-Object {
6
foreach ($keyword in $AlertKeywords) {
7
if ($_ -like "*$keyword*") {
8
# Send alert to DevOps Hub
9
$alertData = @{
10
agent_name = $env:COMPUTERNAME
11
level = "error"
12
message = $_
13
timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ")
14
} | ConvertTo-Json
15
16
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 permissions
2
sudo useradd -m -s /bin/bash -G docker azuredevopsagent
3
sudo passwd -l azuredevopsagent # Lock password authentication
4
5
# Configure sudo permissions
6
echo "azuredevopsagent ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/systemctl restart azuredevops-agent" | sudo tee /etc/sudoers.d/azuredevops-agent
7
8
# Set up directory permissions
9
sudo chown -R azuredevopsagent:azuredevopsagent /home/azuredevopsagent
10
sudo chmod 750 /home/azuredevopsagent

Network security#

1
# Configure firewall rules
2
sudo ufw enable
3
sudo ufw allow out 443 # HTTPS to Azure DevOps
4
sudo ufw allow out 80 # HTTP redirects
5
sudo ufw deny in 22 # Deny incoming SSH
6
sudo ufw deny in 80 # Deny incoming HTTP
7
sudo ufw deny in 443 # Deny incoming HTTPS

Token security#

1
# Store PAT token securely
2
echo "YOUR_PAT_TOKEN" | sudo tee /etc/azuredevops-agent-token
3
sudo chown azuredevopsagent:azuredevopsagent /etc/azuredevops-agent-token
4
sudo chmod 600 /etc/azuredevops-agent-token
5
6
# Reference in agent configuration
7
PAT_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.