Azure Pipelines self-hosted agents: install, register, run in Docker and autoscale
An Azure Pipelines self-hosted agent is the open-source agent program running on a machine you control and registered into an agent pool. You download it from Organization settings, Agent pools, register it with ./config.sh --unattended and a PAT that has the Agent Pools (read, manage) scope, install it as a service with svc.sh, and target the pool with pool: in your YAML.
Agents, pools and when to self-host
Azure DevOps groups agents into agent pools at the organization level. Projects get access to a pool, and every job in a pipeline asks for a pool plus optional demands. The agent polls Azure DevOps over HTTPS, so the machine needs no inbound ports.
Microsoft-hosted agents cap a job at 360 minutes on the paid tier and give you a fixed VM size. You self-host when builds need more cores or memory, access to a private network, a GPU, tools that take too long to install on every run, or caches that survive between jobs.
Prerequisites
- An Azure DevOps organization and pool administrator rights (organization owners have them).
- A Linux host on a supported distribution. The 4.x agent runs on .NET 8 and supports Ubuntu 20.04, 22.04 and 24.04, Debian 12, RHEL 8 and 9, and others on x64, plus Ubuntu and Debian on ARM64. The agent ships its own .NET runtime.
- Git 2.9.0 or newer.
- Outbound HTTPS to
dev.azure.com,*.dev.azure.com,login.microsoftonline.comanddownload.agent.dev.azure.com(the agent package host since the CDN change in 2025).
Choose an authentication method
The agent needs credentials only during registration. After that it uses its own key pair to talk to Azure DevOps. Azure DevOps Services supports three registration methods:
- Personal access token (PAT) with the Agent Pools (read, manage) scope and nothing else. One PAT can register many agents, and an expired PAT does not break agents that are already registered.
- Service principal (
--auth SP, agent 3.227.1 and newer). Add the Entra ID app to the pool's Security tab with the Administrator role. Pick this for automation; it keeps the registration identity away from a personal account. - Device code flow, for an interactive setup on a machine without a browser.
Install and register on Linux
In Organization settings, Agent pools, create a pool (for example linux-builders), open Agents, New agent, Linux and copy the download URL for your architecture. Then create a dedicated user and install:
sudo useradd -m -s /bin/bash azpagent
sudo -iu azpagent
mkdir ~/myagent && cd ~/myagent
curl -fsSLo agent.tar.gz "<download URL from the New agent dialog>"
tar zxf agent.tar.gz
exit
sudo ~azpagent/myagent/bin/installdependencies.sh
Register without prompts:
sudo -iu azpagent
cd ~/myagent
./config.sh --unattended \
--url https://dev.azure.com/your-org \
--auth pat \
--token "$AZP_PAT" \
--pool linux-builders \
--agent "$(hostname)" \
--work _work \
--replace \
--acceptTeeEula
--replace takes over an existing agent of the same name, which you want when you rebuild a machine. Every flag also works as an environment variable: upper-case the name and prefix it with VSTS_AGENT_INPUT_, so VSTS_AGENT_INPUT_TOKEN replaces --token and keeps the PAT out of your shell history. ./config.sh --help lists the full set for your agent version.
Test it in the foreground with ./run.sh, queue a job against the pool, then stop it with Ctrl+C.
Run the agent as a service
Configuration generates svc.sh, which installs a systemd unit named vsts.agent.{org}.{agent}.service:
cd /home/azpagent/myagent
sudo ./svc.sh install azpagent
sudo ./svc.sh start
sudo ./svc.sh status
The service cannot run as root. It snapshots PATH and a few other variables into .env and .path at install time. After you install new tools, run ./env.sh and restart the service so the agent advertises the new capabilities. To remove an agent, run sudo ./svc.sh stop, sudo ./svc.sh uninstall and ./config.sh remove.
For one-job agents, skip the service and start the agent with ./run.sh --once. It accepts one job and exits, and your orchestrator replaces the machine or container.
Run agents in Docker and Kubernetes
Microsoft does not publish a ready-made agent image. The Docker agent guide gives you a Dockerfile (Ubuntu 22.04 or 24.04, or Alpine) and a start.sh that downloads the newest agent, registers it, runs it and deregisters it on exit. Build and run it:
docker build --tag azp-agent:linux --file ./azp-agent-linux.dockerfile .
docker run -d \
-e AZP_URL="https://dev.azure.com/your-org" \
-e AZP_TOKEN="<PAT>" \
-e AZP_POOL="linux-builders" \
-e AZP_AGENT_NAME="docker-agent-1" \
--name azp-agent-1 \
azp-agent:linux --once
Arguments after the image name go to run.sh, so --once gives you a container that takes one job and stops. Service principal credentials work through AZP_CLIENTID, AZP_CLIENTSECRET and AZP_TENANTID. The script sets VSO_AGENT_IGNORE so the token never shows up as a capability.
On Kubernetes, the same image runs as a Deployment that reads AZP_URL, AZP_TOKEN and AZP_POOL from a Secret. A Deployment keeps a fixed replica count; it does not react to the queue. For queue-based scaling of container agents, teams use KEDA's azure-pipelines scaler, which is a community project outside Microsoft's agent docs. Docker tasks inside the pod need access to a Docker daemon, and AKS 1.19+ uses containerd, so plan for a separate build service (BuildKit, Kaniko) instead of mounting the host socket.
Pools, demands and capabilities
Each agent advertises capabilities: system ones it detects (OS, installed tools, every environment variable) and user ones you add in the agent's Capabilities tab. A job lists demands, and Azure DevOps only sends it to agents whose capabilities satisfy all of them.
jobs:
- job: build
pool:
name: linux-builders
demands:
- docker
- Agent.OS -equals Linux
- gpu -equals a10
steps:
- script: make test
A bare name like docker checks that the capability exists. -equals compares the value. Use user capabilities such as gpu or network=prod as the equivalent of labels on other CI systems. The vmImage key only applies to Microsoft-hosted pools; self-hosted pools take name.
Autoscaling: Managed DevOps Pools and scale set agents
Azure DevOps has two built-in ways to scale self-hosted agents with demand.
Azure Virtual Machine Scale Set agents (elastic pools) attach a scale set in your own subscription to a pool. Azure DevOps adds and removes VMs, keeps a standby count, and can tear down each VM after one job. It scales out in percentage steps of the maximum pool size, supports a single image per pool, and draws on your subscription's compute quota.
Managed DevOps Pools is the newer service that Microsoft recommends for new autoscaling pools. The VMs live in a Microsoft-managed subscription, and you create the pool as an Azure resource. You get:
- Stateless pools (a fresh agent per job) or stateful pools that keep agents for up to seven days.
- Standby agent schedules, including an automatic mode.
- Multiple images per pool, including the same images as Microsoft-hosted agents, plus Azure Compute Gallery images.
- Virtual network injection, Key Vault certificate download and jobs up to two days long.
- Scale-out in increments of one agent, up to thousands of agents.
Both options cost the same in Azure DevOps terms: self-hosted parallel jobs plus the Azure compute you consume. VM reservations do not apply to Managed DevOps Pools.
Security hardening
- Run the agent under a dedicated non-root user and restrict its directory to that user and admins. The agent folder holds credentials that an attacker could use to impersonate the agent.
- Use separate identities: the account that registers agents should not be the account that runs them.
- Keep secrets away from fork builds. For GitHub repositories, leave Make secrets available to builds of forks off and require a team member's comment before building pull requests from forks.
- Authorize pipelines on a pool one at a time and keep production pools closed to all other pipelines.
- Prefer one-job agents (
--once, stateless Managed DevOps Pools or scale set tear-down) for anything that runs untrusted code. - Protect the agent from out-of-memory kills with a cgroup and a lower
oom_score_adj, so the kernel reclaims job memory first.
Troubleshooting
Registration fails with 401 or 403
The PAT lacks the Agent Pools (read, manage) scope, has expired, or belongs to a user without pool admin rights. Create a new token with only that scope.
Jobs stay queued because no agent satisfies the demands
Compare the job's demands with the agent's Capabilities tab. Capabilities update on agent restart, so restart the service after installing new tools.
Agent shows offline after a reboot
You ran ./run.sh instead of installing the service, or the service user lost access to the agent folder. Check sudo ./svc.sh status and the logs in _diag.
Network or proxy errors
./run.sh --diagnostics runs connectivity checks. Behind a proxy, configure the agent's proxy settings and allow the domains listed in Prerequisites.
Two agents fight over one name
If you register a second machine with --replace while the first still runs, the two conflict for a few minutes until one shuts down. Remove the old agent with ./config.sh remove or give each machine a unique name.
Cost trade-offs
Azure DevOps bills self-hosted concurrency: the number of jobs that run at once. Private projects get one free self-hosted parallel job, plus one per active Visual Studio Enterprise subscriber in the organization. You can register as many agents as you like, but only that many jobs run at once until you buy more parallel jobs. Check the current per-job price on the Azure DevOps pricing page.
On top of that you pay for compute. Static agents cost money around the clock, so a pool sized for Monday-morning peaks sits idle most of the week. Managed DevOps Pools and scale set agents shrink idle time, but standby agents still bill, and your team owns the images, patching and agent upgrades. On Azure DevOps Server, self-hosted concurrency carries no charge; the number of agents is the only limit.
FAQ
Can I use one PAT to register many Azure DevOps agents?
Yes. A PAT with the Agent Pools (read, manage) scope can register any number of agents, and the agent uses it only at registration time.
Do Managed DevOps Pools replace VM scale set agents?
Microsoft describes Managed DevOps Pools as the evolution of scale set agents and recommends it for new autoscaling pools. Scale set agents still work and keep the VMs in your own subscription.
Is there an official Azure Pipelines agent Docker image?
No. You build your own from the Dockerfile and start.sh in Microsoft's documentation, which downloads the current agent at container start.
Can a self-hosted agent run only one job and then exit?
Yes. Start it with ./run.sh --once, or pass --once to the Docker image. Pair it with an orchestrator that starts a replacement.
Related guides: self-hosted GitHub Actions runners, Jenkins agents on demand and TeamCity build agents.
Running agents with cirunner.dev
cirunner.dev provisions a fresh VM per job, registers it with your CI using a token you provide, scales with the queue and destroys the machine after the job. You choose CPU, RAM, x86_64 or arm64, region, base image and an optional GPU, and pricing is usage-based per compute minute.
The service is in early access, launching with GitHub Actions and GitLab CI. Azure DevOps is on the roadmap; vote for it on the early-access form.
Skip the runner fleet
cirunner.dev boots a fresh VM for every Azure DevOps job, registers it, and destroys it when the job ends. Azure DevOps is on our roadmap. Vote for it on the early-access form.
Join early access