{"id":627,"date":"2025-09-02T14:36:59","date_gmt":"2025-09-02T12:36:59","guid":{"rendered":"https:\/\/configroar.com\/?p=627"},"modified":"2025-09-02T14:40:32","modified_gmt":"2025-09-02T12:40:32","slug":"%f0%9f%9a%80-run-azure-devops-self-hosted-agents-in-windows-server-2025-containers","status":"publish","type":"post","link":"https:\/\/configroar.com\/?p=627","title":{"rendered":"\ud83d\ude80 Run Azure DevOps Self-Hosted Agents in Windows Server 2025 Containers"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">If you\u2019ve ever used Azure DevOps pipelines, you probably know the convenience of Microsoft-hosted agents. But sometimes\u2026 they\u2019re just not enough. Maybe you need custom tools, maybe you want builds inside your own network, or maybe you just want to speed things up on a beefy server you already own.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s where <strong>self-hosted agents<\/strong> come in. And here\u2019s the fun part: you can run them neatly packaged as <strong>Windows containers<\/strong> on Windows Server 2025 \ud83d\udda5\ufe0f\ud83d\udc33.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide walks you through the whole process: building an image, running containers, and hooking them into your Azure DevOps pool. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf0 What you\u2019ll need<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Windows Server 2025 machine (physical or VM)<\/li>\n\n\n\n<li>Docker installed (make sure you enabled <strong>Windows containers<\/strong> during setup!)<\/li>\n\n\n\n<li>An <a>Azure DevOps Personal Access Token (PAT)<\/a> with <strong>Agent Pools (read &amp; manage)<\/strong><\/li>\n\n\n\n<li>(Optional) Azure CLI<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1 \u2014 Create the Docker files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Make yourself a folder to work in:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir C:\\DockerFiles\\AzDOAgent\ncd C:\\DockerFiles\\AzDOAgent<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Inside, you\u2019ll need <strong>two files<\/strong>:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udcc4 <code>Dockerfile<\/code><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># escape=`\nFROM mcr.microsoft.com\/windows\/servercore:ltsc2025\nSHELL &#91;\"powershell\",\"-NoLogo\",\"-NoProfile\",\"-Command\"]\n\n# Install PowerShell 7\nRUN Invoke-WebRequest https:\/\/github.com\/PowerShell\/PowerShell\/releases\/download\/v7.4.3\/PowerShell-7.4.3-win-x64.msi -OutFile pwsh.msi ; `\n    Start-Process msiexec.exe -ArgumentList '\/i','pwsh.msi','\/qn','\/norestart' -Wait ; `\n    Remove-Item pwsh.msi -Force\n\n# Install Git\nRUN Invoke-WebRequest https:\/\/github.com\/git-for-windows\/git\/releases\/download\/v2.47.1.windows.1\/Git-2.47.1-64-bit.exe -OutFile git.exe ; `\n    Start-Process .\\git.exe -ArgumentList '\/VERYSILENT','\/NORESTART','\/SUPPRESSMSGBOXES' -Wait ; `\n    Remove-Item .\\git.exe -Force\n\n# Agent home\nRUN New-Item -Type Directory -Path C:\\agent -Force | Out-Null\nWORKDIR C:\\agent\n\n# Defaults consumed by start script\nENV AGENT_VERSION=\"4.260.0\" `\n    AZP_URL=\"\" `\n    AZP_POOL=\"Default\" `\n    AZP_AGENT_NAME=\"\" `\n    AZP_TOKEN=\"\" `\n    AZP_WORK=\"C:\\\\agent\\\\_work\"\n\n# Copy start script\nCOPY start-agent.ps1 C:\\agent\\start-agent.ps1\n\nCMD &#91;\"cmd.exe\",\"\/C\",\"pwsh -File C:\\\\agent\\\\start-agent.ps1\"]<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udcc4 <code>start-agent.ps1<\/code><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>$ErrorActionPreference = 'Stop'\nSet-Location C:\\agent\nfunction Log($m){ Write-Host \"&#91;start-agent] $m\" }\n\n# Validate required env vars\n$need = @('AZP_URL','AZP_TOKEN')\nforeach ($v in $need){\n  $val = &#91;Environment]::GetEnvironmentVariable($v)\n  if (&#91;string]::IsNullOrWhiteSpace($val)){ throw \"$v is required\" }\n}\nif (&#91;string]::IsNullOrWhiteSpace($env:AZP_POOL))       { $env:AZP_POOL = 'Default' }\nif (&#91;string]::IsNullOrWhiteSpace($env:AZP_AGENT_NAME)) { $env:AZP_AGENT_NAME = $env:COMPUTERNAME }\nif (&#91;string]::IsNullOrWhiteSpace($env:AZP_WORK))       { $env:AZP_WORK = 'C:\\agent\\_work' }\nif (&#91;string]::IsNullOrWhiteSpace($env:AGENT_VERSION))  { $env:AGENT_VERSION = '4.260.0' }\n\n# Download agent (pinned to 4.260.0)\n&#91;Net.ServicePointManager]::SecurityProtocol = &#91;Net.SecurityProtocolType]::Tls12\n$ver = $env:AGENT_VERSION\n$zip = \"C:\\agent\\agent.zip\"\n$url = \"https:\/\/download.agent.dev.azure.com\/agent\/$ver\/vsts-agent-win-x64-$ver.zip\"\n\n$max = 5\nfor ($i=1; $i -le $max; $i++) {\n  try {\n    Log \"Downloading agent $ver from $url (attempt $i\/$max)...\"\n    Invoke-WebRequest -Uri $url -OutFile $zip\n    if (Test-Path $zip) { break }\n  } catch {\n    if ($i -eq $max) { throw }\n    Start-Sleep -Seconds (5 * $i)\n  }\n}\n\nExpand-Archive -Path $zip -DestinationPath . -Force\nRemove-Item $zip -Force\nNew-Item -ItemType Directory -Path $env:AZP_WORK -Force | Out-Null\n\n# Configure the agent\nfor ($i=1; $i -le 5; $i++) {\n  try {\n    Log \"Configuring agent (attempt $i\/5)...\"\n    .\\config.cmd --unattended `\n      --url $env:AZP_URL --auth pat --token $env:AZP_TOKEN `\n      --pool $env:AZP_POOL --agent $env:AZP_AGENT_NAME --work $env:AZP_WORK --replace\n    break\n  } catch {\n    if ($i -eq 5) { throw }\n    Start-Sleep -Seconds (5 * $i)\n  }\n}\n\nLog \"Starting agent...\"\n.\\run.cmd<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd28 Step 2 \u2014 Build your image<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker build --isolation=hyperv -t ado-agent:win2025 .<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This builds a shiny new image called <code>ado-agent:win2025<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u25b6\ufe0f Step 3 \u2014 Spin up your agents<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a simple PowerShell snippet to launch two agents:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$AZDO_ORG_URL=\"https:\/\/dev.azure.com\/&lt;your-org&gt;\"\n$AZDO_POOL=\"Personal\"\n$AZDO_PAT=\"&lt;YOUR_PAT&gt;\"   # PAT with Agent Pools (read &amp; manage)\n$AGENT_VER=\"4.260.0\"\n$COUNT=2\n\n1..$COUNT | ForEach-Object {\n  $name = \"ado-win-$_\"\n  docker run -d --restart=always --name $name `\n    -e AZP_URL=$AZDO_ORG_URL `\n    -e AZP_POOL=$AZDO_POOL `\n    -e AZP_TOKEN=$AZDO_PAT `\n    -e AZP_AGENT_NAME=$name `\n    -e AGENT_VERSION=$AGENT_VER `\n    ado-agent:win2025\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udca1 The <code>--restart=always<\/code> flag means your agents will automatically come back online if the server or Docker service restarts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udc40 Step 4 \u2014 Check in DevOps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Head over to <strong>Organization Settings \u2192 Agent Pools<\/strong> in Azure DevOps.<br>Open your pool (e.g., <code>Personal<\/code>) and\u2026 voil\u00e0! \u2728 You should see your new agents appear online.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 Step 5 \u2014 Test it with a pipeline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a simple pipeline in your project:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pool: Personal\nsteps:\n- powershell: |\n    echo \"Hello from $(Agent.Name) \ud83c\udf89\"\n    git --version\n    dotnet --version\n  displayName: \"Sanity check\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run it \u2014 and watch your brand-new containerized Windows 2025 agent do its job.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf Wrap-up<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">And that\u2019s it! You now have <strong>Azure DevOps self-hosted agents running as Windows containers<\/strong> on Windows Server 2025.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Why this rocks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd04 Easily scale \u2014 just spin up more containers<\/li>\n\n\n\n<li>\ud83e\udde9 Stay in control \u2014 install whatever tools you need inside your image<\/li>\n\n\n\n<li>\ud83d\udcaa Stable \u2014 agents restart automatically on reboot<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udc49 Next step: push your image to a private registry (like ACR or Docker Hub) and you\u2019ll be able to roll it out across multiple servers in no time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019ve ever used Azure DevOps pipelines, you probably know the convenience of Microsoft-hosted agents. But sometimes\u2026 they\u2019re just not enough. Maybe you need custom<\/p>\n","protected":false},"author":2,"featured_media":630,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[77,79,78],"tags":[82,75,81],"class_list":["post-627","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ci-cd","category-containers","category-devops","tag-containers","tag-devops","tag-docker"],"_links":{"self":[{"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/posts\/627","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/configroar.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=627"}],"version-history":[{"count":3,"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/posts\/627\/revisions"}],"predecessor-version":[{"id":631,"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/posts\/627\/revisions\/631"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/configroar.com\/index.php?rest_route=\/wp\/v2\/media\/630"}],"wp:attachment":[{"href":"https:\/\/configroar.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=627"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/configroar.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=627"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/configroar.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=627"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}