{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"},{"name":"Jendrik Brack","url":"https://daily-devops.net/authors/jendrik/"}],"description":"Recent content in Terraform: Declarative Infrastructure as Code with HCL on Daily DevOps \u0026 .NET","favicon":"https://daily-devops.net/images/logo_hu_5d8cd2f778ca1cc7.png","feed_url":"https://daily-devops.net/tags/terraform/feed.json","home_page_url":"https://daily-devops.net/tags/terraform/","icon":"https://daily-devops.net/images/logo_hu_c1e6cf616d4e7aa4.png","items":[{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eMost teams that move to Managed DevOps Pools make the same two decisions in the first hour: pick a virtual machine (VM) size and click Create. Everything else stays at the defaults. Then, three weeks later, someone complains that pipelines wait forever for an agent, or someone from finance asks why the pool bill looks like a small server farm.\u003c/p\u003e\n\u003cp\u003eBoth complaints usually trace back to the same place: the scaling configuration nobody looked at. Managed DevOps Pools (MDP) gives you three separate dials, and they influence each other. Agent state decides what happens after a job. Standby mode decides what is waiting before a job. Maximum agents decides how far any of it can go.\u003c/p\u003e\n\u003cp\u003eWe run MDP in production for our own pipelines, and these dials are what let us steer how much load our builds and deployments put on our systems, while the monthly bill stays predictable. I will come back to how that looks in practice once the mechanics are clear.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s go through the three dials one by one, with configuration you can paste into a template. The examples are written in Terraform\u0026rsquo;s HashiCorp Configuration Language (HCL), with raw Azure Resource Manager (ARM) JSON (JavaScript Object Notation) and Azure command-line interface (CLI) snippets where the underlying schema matters. Everything is based on the \u003ca href=\"https://learn.microsoft.com/azure/devops/managed-devops-pools/configure-scaling\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eofficial scaling documentation\u003c/a\u003e and the \u003ca href=\"https://learn.microsoft.com/azure/devops/managed-devops-pools/configure-pool-settings\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003epool settings reference\u003c/a\u003e. The exact versions are listed with the complete pool example further down.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-actually-limits-how-many-agents-you-get\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#what-actually-limits-how-many-agents-you-get\" title=\"What actually limits how many agents you get\"\u003eWhat actually limits how many agents you get\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe hard ceiling is \u003ccode\u003emaximumConcurrency\u003c/code\u003e, shown as Maximum agents in the portal. It is the maximum number of agents that can be provisioned at the same time in the pool, nothing more.\u003c/p\u003e\n\u003cp\u003eTwo things are easy to confuse here:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eMaximum agents is not parallelism.\u003c/strong\u003e Your Azure DevOps organization has its own count of self-hosted parallel jobs, and that number decides how many jobs run concurrently. A pool with 20 agents and an organization with 4 self-hosted parallel jobs runs 4 jobs at a time. The other 16 agent slots are never used, and if your standby settings keep them alive, you pay for idle machines.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMaximum agents is bounded by quota.\u003c/strong\u003e If your subscription cannot provide the cores for the VM stock keeping unit (SKU) multiplied by your maximum agents, pool creation fails with an error naming the cores required and your current limit. Not every SKU exists in every region either, so check regional availability before you standardize on one.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eresource\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;azurerm_managed_devops_pool\u0026#34; \u0026#34;build\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  name\u003c/span\u003e                \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;build-pool\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  maximum_concurrency\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e8\u003c/span\u003e\u003cspan class=\"c1\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e  # ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI treat this number as a budget guardrail first and a capacity setting second. Size it for your realistic peak, request quota for it early (quota requests have their own lead time), and align it with the parallel jobs you actually bought.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"stateless-or-stateful-decide-what-happens-after-the-job\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#stateless-or-stateful-decide-what-happens-after-the-job\" title=\"Stateless or stateful: decide what happens after the job\"\u003eStateless or stateful: decide what happens after the job\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe agent state is configured through \u003ccode\u003eagentProfile.kind\u003c/code\u003e in ARM, and through the mutually exclusive \u003ccode\u003estateless_agent\u003c/code\u003e and \u003ccode\u003estateful_agent\u003c/code\u003e blocks in Terraform.\u003c/p\u003e\n\u003cp\u003eStateless is the default. A new agent is procured for every job and discarded afterwards. You get a clean machine each time, nothing leaks from one run into the next, and the documentation explicitly recommends stateless pools as a defense against supply chain attacks. For a pipeline that builds third-party dependencies and signs artifacts, this is the setting I would defend in any architecture review.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003estateless_agent\u003c/span\u003e {}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eStateful lets agents serve multiple jobs. The typical reason is build performance: warm package caches, restored solutions, incremental build output. You configure two timers:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003emaxAgentLifetime\u003c/code\u003e (\u003ccode\u003emaximum_agent_lifetime\u003c/code\u003e in Terraform): the maximum time an agent lives before it is shut down and discarded. Format \u003ccode\u003edd.hh:mm:ss\u003c/code\u003e, default and maximum is seven days (\u003ccode\u003e7.00:00:00\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003egracePeriodTimeSpan\u003c/code\u003e (\u003ccode\u003egrace_period_time_span\u003c/code\u003e in Terraform): how long an idle agent waits for new jobs after all current and queued jobs finish. Same format, and the default is no grace period.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003estateful_agent\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  maximum_agent_lifetime\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;7.00:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  grace_period_time_span\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;00:30:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThere are a few behaviors here that surprise people:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWithout a grace period, a stateful agent is not stateful for long. If no job is queued, no standby schedule is active and no grace period is configured, the agent is discarded right after the job, and its state with it.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emaxAgentLifetime\u003c/code\u003e wins over everything. Even with a 24/7 standby schedule, an agent is recycled once it hits the lifetime. Configure three days and your \u0026ldquo;always on\u0026rdquo; agents restart every three days.\u003c/li\u003e\n\u003cli\u003eA running job is not killed by the lifetime timer, but individual jobs in Managed DevOps Pools can run for two days at most, regardless of the configured lifetime.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe documentation calls grace periods the most cost-effective way to run stateful pools for pipelines with consistent load, and notably they do not require standby mode at all. If your team commits steadily during the day, a 20 to 30 minute grace period keeps warm agents around between builds without a schedule to maintain.\u003c/p\u003e\n\u003cp\u003eThe security trade-off is real, though. A stateful agent that ran a pull request validation from an untrusted branch is a different asset than a fresh VM. I keep stateful pools for trusted main-branch builds and leave everything else stateless. If you want to see how gates fit into that picture, my article on \u003ca href=\"/posts/continuous-deployment-security-gates/\"\u003econtinuous deployment security gates\u003c/a\u003e covers the other half of the story.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"standby-agents-the-three-modes\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#standby-agents-the-three-modes\" title=\"Standby agents: the three modes\"\u003eStandby agents: the three modes\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWithout standby agents, every job waits for a machine. The documentation puts the on-demand wait at anywhere from a few moments up to about 15 minutes. With a standby agent in the ready state, startup, connection and job start typically take between 10 seconds and a minute, depending on SKU speed, image and networking.\u003c/p\u003e\n\u003cp\u003eA standby agent is capacity you pay for in advance, whether a job arrives or not. Every standby decision comes down to how much waiting that money should buy.\u003c/p\u003e\n\u003cp\u003eStandby mode lives in \u003ccode\u003eagentProfile.resourcePredictionsProfile\u003c/code\u003e in ARM. In Terraform you nest a \u003ccode\u003emanual_resource_prediction\u003c/code\u003e or \u003ccode\u003eautomatic_resource_prediction\u003c/code\u003e block (at most one of them, none means off) inside the agent block. There are three states: off, manual and automatic. The standby count in any scheme cannot exceed Maximum agents.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"off-agents-on-demand\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#off-agents-on-demand\" title=\"Off: agents on demand\"\u003eOff: agents on demand\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThis is the default. You configure nothing and omit the \u003ccode\u003eresourcePredictionsProfile\u003c/code\u003e section. It is perfectly fine for nightly builds, rarely used pools and any workload where nobody stares at a pipeline waiting for it to start.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"manual-you-know-your-usage-pattern\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#manual-you-know-your-usage-pattern\" title=\"Manual: you know your usage pattern\"\u003eManual: you know your usage pattern\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eManual mode is for teams who can describe their workload with a calendar. You set a \u003ccode\u003etimeZone\u003c/code\u003e once for the whole pool and a \u003ccode\u003edaysData\u003c/code\u003e list. The list has either one item (all-week scheme) or seven items starting with \u003cstrong\u003eSunday\u003c/strong\u003e. Each item maps times to standby counts, and a count holds until the next entry.\u003c/p\u003e\n\u003cp\u003eHere is a weekday scheme for a team in Germany: four standby agents from 07:00 to 18:00, Monday to Friday. In Terraform, the \u003ccode\u003eazurerm\u003c/code\u003e provider flattens \u003ccode\u003edaysData\u003c/code\u003e into one \u003ccode\u003e\u0026lt;weekday\u0026gt;_schedule\u003c/code\u003e block per entry (plus the integer attribute \u003ccode\u003eall_week_schedule\u003c/code\u003e for the all-week scheme) and \u003ccode\u003etimeZone\u003c/code\u003e into \u003ccode\u003etime_zone_name\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003estateless_agent\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003emanual_resource_prediction\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    time_zone_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;W. Europe Standard Time\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003emonday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003emonday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etuesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etuesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ewednesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ewednesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ethursday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ethursday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efriday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efriday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe same scheme as raw ARM template JSON, which is the schema the service actually consumes:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e\u0026#34;agentProfile\u0026#34;\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;kind\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Stateless\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;resourcePredictionsProfile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;kind\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Manual\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;resourcePredictions\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;timeZone\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;W. Europe Standard Time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;daysData\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is where most manual schedules go wrong: \u003cstrong\u003ean empty \u003ccode\u003edaysData\u003c/code\u003e item does not mean zero agents. It means no change.\u003c/strong\u003e Counts do not reset at midnight or at the end of the week. If you forget the \u003ccode\u003e\u0026quot;18:00:00\u0026quot;: 0\u003c/code\u003e entry on Friday, your four standby agents run through the weekend and into Monday. The schedule above is correct because every working day closes with an explicit zero.\u003c/p\u003e\n\u003cp\u003eThe same mechanic lets you span midnight: put \u003ccode\u003e\u0026quot;09:00:00\u0026quot;: 1\u003c/code\u003e in one day and \u003ccode\u003e\u0026quot;17:00:00\u0026quot;: 0\u003c/code\u003e in the next. The documentation also shows a ramp such as one agent from midnight, ten from 09:00 and none after 17:00.\u003c/p\u003e\n\u003cp\u003eTwo notes on the time zone. The documentation points to \u003ccode\u003eTimeZoneInfo.GetSystemTimeZones\u003c/code\u003e for valid names and uses Windows-style identifiers such as \u003ccode\u003eEastern Standard Time\u003c/code\u003e in its examples, so verify your value against that list instead of guessing. And there is only one \u003ccode\u003etimeZone\u003c/code\u003e per pool.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"automatic-let-the-history-decide\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#automatic-let-the-history-decide\" title=\"Automatic: let the history decide\"\u003eAutomatic: let the history decide\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eAutomatic mode looks at the past three weeks of usage (if available), groups queued sessions of the pool into five-minute periods and assigns a percentile to each hour. You control the percentile with \u003ccode\u003epredictionPreference\u003c/code\u003e:\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eValue\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003ePercentile\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003ePortal label\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eMostCostEffective\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e10th\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMost cost effective\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eMoreCostEffective\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e25th\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMore cost effective\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eBalanced\u003c/code\u003e (default)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e50th\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eBalanced\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eMorePerformance\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e75th\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMore performance\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eBestPerformance\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e90th\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eBest performance\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003estateless_agent\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eautomatic_resource_prediction\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    prediction_preference\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Balanced\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI like automatic mode for teams whose load is real but irregular, because it removes the schedule maintenance burden. The catch is that it is backward looking. A new project, a release crunch or a holiday week is not in the history, so the first Monday after a big change can still feel slow. It also works on percentiles, so \u003ccode\u003eBestPerformance\u003c/code\u003e does not mean \u0026ldquo;never wait\u0026rdquo;. It provisions for the 90th percentile of the demand observed in each hour, so spikes above that still wait.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"azure-cli-a-different-json-shape\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#azure-cli-a-different-json-shape\" title=\"Azure CLI: a different JSON shape\"\u003eAzure CLI: a different JSON shape\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe Azure CLI takes the profile as a JSON file through \u003ccode\u003e--agent-profile\u003c/code\u003e on \u003ccode\u003eaz mdp pool create\u003c/code\u003e and \u003ccode\u003eaz mdp pool update\u003c/code\u003e. Note that the shape differs from the ARM template one: the kind becomes a property name.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eaz mdp pool update \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --name build-pool \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --resource-group rg-devops-pools \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --agent-profile agent-profile.json\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe following \u003ccode\u003eagent-profile.json\u003c/code\u003e contains the automatic profile:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;Stateless\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;resourcePredictionsProfile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;Automatic\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026#34;predictionPreference\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Balanced\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd this is the stateful variant from the documentation:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;Stateful\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;maxAgentLifetime\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;7.00:00:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026#34;gracePeriodTimeSpan\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;00:30:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf you copy a snippet from a template into a CLI file, or the other way around, the result is a validation error that does not point at the real cause. I have watched that cost people an afternoon.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"standby-agents-across-multiple-images\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#standby-agents-across-multiple-images\" title=\"Standby agents across multiple images\"\u003eStandby agents across multiple images\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eA pool can offer several images, for example Windows and Ubuntu. The standby capacity is then divided by the \u003ccode\u003ebuffer\u003c/code\u003e property in the \u003ccode\u003eimages\u003c/code\u003e section, which is a percentage of the standby agents for that image. You can use \u003ccode\u003e'*'\u003c/code\u003e to distribute equally, or integers that must total 100. The REST application programming interface (API) defines \u003ccode\u003ebuffer\u003c/code\u003e as a string (default \u003ccode\u003e\u0026quot;*\u0026quot;\u003c/code\u003e), and the \u003ccode\u003eazurerm\u003c/code\u003e provider\u0026rsquo;s \u003ccode\u003ebuffer\u003c/code\u003e is a string too (\u003ccode\u003e\u0026quot;*\u0026quot;\u003c/code\u003e or \u003ccode\u003e\u0026quot;0\u0026quot;\u003c/code\u003e to \u003ccode\u003e\u0026quot;100\u0026quot;\u003c/code\u003e).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003evirtual_machine_scale_set_fabric\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  sku_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Standard_D4ads_v5\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eimage\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    well_known_image_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ubuntu-22.04\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    buffer\u003c/span\u003e                \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;70\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eimage\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    well_known_image_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;windows-2022\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    buffer\u003c/span\u003e                \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;30\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWith a total of ten standby agents this gives seven Ubuntu and three Windows machines. Two details from the documentation matter in practice:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eJobs without an \u003ccode\u003eImageOverride\u003c/code\u003e demand target the first image.\u003c/strong\u003e If your pipelines do not state the image, the three Windows standby agents wait for nobody, while every Ubuntu job beyond the seven ready agents falls back to on-demand provisioning.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003eImageVersionOverride\u003c/code\u003e demands defeat standby agents.\u003c/strong\u003e Standby agents are provisioned with the image versions from the pool configuration, so a pipeline that pins a different image version always starts a fresh agent and pays the on-demand wait. Forcing an older task agent (for example through the \u003ccode\u003eAgent.Version\u003c/code\u003e demand) is a smaller penalty, but still a delay: the agent has to be downloaded again after the machine is allocated.\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\n\n\u003ch2 id=\"a-complete-pool-assembled\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#a-complete-pool-assembled\" title=\"A complete pool, assembled\"\u003eA complete pool, assembled\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFor reference, this is a complete pool resource. It uses the \u003ccode\u003eazurerm_managed_devops_pool\u003c/code\u003e resource from the AzureRM provider, which exposes agent state, manual and automatic standby schedules and image buffers as first-class blocks, so there is no need for the \u003ccode\u003eazapi\u003c/code\u003e provider here. Compared with the ARM schema, the provider hides discriminators such as the organization kind and the \u003ccode\u003eVmss\u003c/code\u003e fabric kind, and you attach the pool to a Dev Center project through \u003ccode\u003edev_center_project_id\u003c/code\u003e. Check the \u003ca href=\"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_devops_pool\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eprovider documentation\u003c/a\u003e for the version you pin. Treat it as a starting point, not a drop-in: parameter values, project association and permissions depend on your environment.\u003c/p\u003e\n\u003cp\u003eOn versions: the \u003ccode\u003eazurerm_managed_devops_pool\u003c/code\u003e resource is available since AzureRM provider 4.68.0, and all Terraform snippets in this article pass \u003ccode\u003eterraform validate\u003c/code\u003e on 4.68.0 and 5.7.0. OpenTofu resolves the same \u003ccode\u003ehashicorp/azurerm\u003c/code\u003e provider from its own registry, so the HCL carries over unchanged, although I only ran the validation with Terraform. The ARM snippets target API version \u003ccode\u003e2026-06-02\u003c/code\u003e, the latest stable version of \u003ccode\u003eMicrosoft.DevOpsInfrastructure/pools\u003c/code\u003e. The scaling documentation still shows \u003ccode\u003e2025-09-20\u003c/code\u003e, but the agent profile, standby and image buffer properties used here are identical in both versions; \u003ccode\u003e2026-06-02\u003c/code\u003e adds features on top, such as mixing several VM sizes in one pool. The \u003ccode\u003eaz mdp\u003c/code\u003e CLI extension calls its own pinned API version, but the \u003ccode\u003e--agent-profile\u003c/code\u003e shape shown above is the one from the documentation.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-hcl\" data-lang=\"hcl\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003evariable\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;dev_center_project_id\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  type\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003evariable\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;azure_devops_organization_url\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  type\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eresource\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;azurerm_managed_devops_pool\u0026#34; \u0026#34;build\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  name\u003c/span\u003e                  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;build-pool\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  resource_group_name\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;rg-devops-pools\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  location\u003c/span\u003e              \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;westeurope\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  dev_center_project_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003evar\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003edev_center_project_id\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  maximum_concurrency\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eazure_devops_organization\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eorganization\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      url\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003evar\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003eazure_devops_organization_url\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      parallelism\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003estateless_agent\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003emanual_resource_prediction\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      time_zone_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;W. Europe Standard Time\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003emonday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003emonday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003etuesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003etuesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ewednesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ewednesday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ethursday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ethursday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003efriday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;07:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003efriday_schedule\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        time\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;18:00:00\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e        count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003evirtual_machine_scale_set_fabric\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e    sku_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Standard_D4ads_v5\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eimage\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      well_known_image_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ubuntu-22.04\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e      buffer\u003c/span\u003e                \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNotice that \u003ccode\u003eparallelism\u003c/code\u003e in the \u003ccode\u003eorganization\u003c/code\u003e block matches \u003ccode\u003emaximum_concurrency\u003c/code\u003e (\u003ccode\u003emaximumConcurrency\u003c/code\u003e in ARM). According to the documentation, this property is the number of agents an organization can use, and the sum across all organizations of a pool must match the maximum agents. It is a pool-internal split for shared pools, not your purchased parallel jobs. Those are a separate constraint, as described in the first section.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"how-we-use-it-cascading-pipelines-and-fan-out-deployments\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#how-we-use-it-cascading-pipelines-and-fan-out-deployments\" title=\"How we use it: cascading pipelines and fan-out deployments\"\u003eHow we use it: cascading pipelines and fan-out deployments\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOur own pipeline landscape is a good stress test for all of this. Two patterns dominate it. The first is cascading pipelines: a build completes, its pipeline completion trigger starts the next pipeline, which in turn kicks off further pipelines. The second is fan-out deployments, where a small change, sometimes a single line in a shared template or configuration, starts a large number of deployments at once.\u003c/p\u003e\n\u003cp\u003eBoth patterns produce bursts, not steady load. On a static fleet of self-hosted agents, a burst like that means either long queues or a fleet sized for the worst case that sits idle most of the day. With MDP we handle it with two settings:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eMaximum agents is our load valve.\u003c/strong\u003e Every running agent is also a client of something: package feeds, artifact storage, and above all the target systems of a deployment. Capping \u003ccode\u003emaximumConcurrency\u003c/code\u003e caps how many of those clients exist at the same time. When a fan-out starts more deployments than the pool allows, the remaining jobs wait in the queue and run as agents free up, instead of hitting our environments all at once. The ceiling turns a deployment storm into a controlled wave.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStandby agents keep the cascade moving.\u003c/strong\u003e In a chain of pipelines, every stage that waits for a fresh machine adds its provisioning time to the total. A small number of standby agents during working hours removes most of that delay, because the next stage usually finds a ready agent.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe effect on cost is the part I value most. The ceiling is a hard upper bound on what the pool can spend at any moment, and standby capacity is a number we chose deliberately rather than something that grows with demand. A burst makes the queue longer, not the bill. That combination gives these pipelines a lot of throughput while the costs stay consistently under control.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"recommendations-by-scenario\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#recommendations-by-scenario\" title=\"Recommendations by scenario\"\u003eRecommendations by scenario\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere is no universally correct setup. This is what I would start with and why.\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eScenario\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eAgent state\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eStandby mode\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eReasoning\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eSmall team, a handful of builds per day\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateless\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eOff, or manual with 1 agent during working hours\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eWaiting a few minutes now and then costs less than idle machines. Add one standby agent only when the wait annoys people.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eSingle-site team with clear peak hours\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateless for pull requests, stateful with grace period for trusted builds\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eManual weekday scheme, explicit zero at closing time\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eThe schedule is predictable, so use it. Size the count for the typical burst, not the worst day.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eSteady load, warm caches matter\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateful, 20 to 30 minute grace period, sensible \u003ccode\u003emaxAgentLifetime\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eOff\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePer the documentation the cheapest stateful option, and no schedule to maintain.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eIrregular but real load\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateless\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eAutomatic, start at \u003ccode\u003eBalanced\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eLet history do the work, then move one step in either direction after a few weeks.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eGlobal team across time zones\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateless\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eAutomatic, or one manual scheme in Coordinated Universal Time (UTC)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eA manual schedule has a single time zone per pool. Either accept a broad window that covers all regions or split into regional pools.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eNightly or scheduled builds\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateless\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eOff\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eNobody is waiting. A few minutes of provisioning time at 02:00 is free.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eCascading pipelines or fan-out deployments\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStateless\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eManual, a few agents during working hours\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eUse Maximum agents as the throttle that protects target systems, and standby agents to cut the provisioning delay between stages.\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eFor the global team, I would lean on automatic mode first. If your regions have very different peaks, separate pools per region also give you cleaner cost attribution, at the price of splitting your parallel job allocation.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"pitfalls-i-would-check-first\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#pitfalls-i-would-check-first\" title=\"Pitfalls I would check first\"\u003ePitfalls I would check first\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhen a pool \u0026ldquo;scales badly\u0026rdquo;, this is my checklist, in order:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eAre there actually ready agents when the job is queued? Jobs fall back to on-demand provisioning when more jobs are queued than standby agents exist, when they are queued outside the schedule, and when the standby count is empty.\u003c/li\u003e\n\u003cli\u003eDoes the pipeline target the right image? Without \u003ccode\u003eImageOverride\u003c/code\u003e, jobs go to the first image.\u003c/li\u003e\n\u003cli\u003eDoes the pipeline pin an image or agent version? A pinned image version bypasses standby agents, and a forced agent version adds a download.\u003c/li\u003e\n\u003cli\u003eIs the Friday zero missing in a manual schedule?\u003c/li\u003e\n\u003cli\u003eIs \u003ccode\u003emaximumConcurrency\u003c/code\u003e higher than your organization\u0026rsquo;s self-hosted parallel jobs, or lower than the standby count you configured?\u003c/li\u003e\n\u003cli\u003eAre proxies, firewalls or virtual network settings slowing down the agent connection?\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eBefore you add more standby agents, check whether utilization justifies the ones you have. The cost guidance in the documentation says the same thing: in manual mode, look at the historical utilization of standby agents and reduce the count if it is not used. Details are in \u003ca href=\"https://learn.microsoft.com/azure/devops/managed-devops-pools/manage-costs\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eManage cost and performance\u003c/a\u003e.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"/posts/azure-devops-managed-pools-scaling/#conclusion\" title=\"Conclusion\"\u003eConclusion\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eScaling in Managed DevOps Pools is \u003cstrong\u003ea latency purchase, not a capacity purchase\u003c/strong\u003e. Capacity is bounded by quota and parallelism. What you buy with standby agents is the absence of waiting.\u003c/p\u003e\n\u003cp\u003eMy practical recommendation: start stateless with standby off, measure how long jobs actually wait, and add the cheapest mechanism that fixes the wait. For steady load that is a grace period on a stateful pool for trusted builds. For predictable peaks it is a manual weekday schedule with explicit zeros. For irregular load it is automatic mode at \u003ccode\u003eBalanced\u003c/code\u003e. And whatever you choose, put it in Terraform, review it like code and revisit it after a month of real data.\u003c/p\u003e\n","date_modified":"2026-10-03T07:46:39Z","date_published":"2026-10-03T09:00:00+02:00","id":"https://daily-devops.net/posts/azure-devops-managed-pools-scaling/","language":"en","summary":"Stateless or stateful, standby schedules, automatic prediction and image buffers: how to scale Managed DevOps Pools, with Terraform, ARM and CLI examples.","tags":["azuredevops","azure","cicd","infrastructure-as-code","terraform","opentofu"],"title":"Managed DevOps Pools: Scale Agents Without Burning Money","url":"https://daily-devops.net/posts/azure-devops-managed-pools-scaling/"}],"language":"en","title":"Terraform: Declarative Infrastructure as Code with HCL on Daily DevOps \u0026 .NET","version":"https://jsonfeed.org/version/1.1"}