{"id":368,"date":"2026-01-23T07:06:28","date_gmt":"2026-01-23T07:06:28","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=368"},"modified":"2026-01-23T07:06:28","modified_gmt":"2026-01-23T07:06:28","slug":"setup-self-hosted-runner-azure-devops-pipeline","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=368","title":{"rendered":"How to Setup Self Hosted Linux Runner for Azure DevOps?"},"content":{"rendered":"<p>In this blog, you will learn how to set up a <strong>self-hosted linux based runner<\/strong> for <strong>Azure DevOps<\/strong> pipeline.<\/p>\n<p>By the end of this blog, you will learn<\/p>\n<ol>\n<li>What is a self-hosted runner?<\/li>\n<li>How to set up a self-hosted Linux runner in Azure DevOps?<\/li>\n<li>How to test self hosted runner in Azure DevOps?<\/li>\n<li>Best practices for self-hosted runners.<\/li>\n<\/ol>\n<p>Before we begin the setup, lets understand what is a self-hosted runner in Azure.<\/p>\n<h2 id=\"what-is-a-self-hosted-runner\">What is a Self-Hosted Runner ?<\/h2>\n<p><a href=\"https:\/\/devopscube.com\/github-actions-runner-aws-eks\/\" rel=\"noreferrer\">Self-hosted runners<\/a> are servers or containers that we manage to run our Azure DevOps pipeline tasks.<\/p>\n<p><strong>Self-hosted<\/strong> means we control the environment where the pipeline runners execute. This setup is typically used for <strong>production-level projects<\/strong>, where we need more control over security, performance, and dependencies.<\/p>\n<p>The following can be a self-hosted runner.<\/p>\n<ol>\n<li>Virtual machine<\/li>\n<li>Physical server<\/li>\n<li><a href=\"https:\/\/devopscube.com\/what-is-a-container-and-how-does-it-work\/\" rel=\"noreferrer\">Container<\/a><\/li>\n<li><a href=\"https:\/\/devopscube.com\/kubernetes-tutorials-beginners\/\" rel=\"noreferrer\">Kubernetes<\/a><\/li>\n<\/ol>\n<p><strong>Organizations generally use self-hosted runners<\/strong> because they can control performance, cost, security, networking, compliance, and other aspects.<\/p>\n<p>By having control over the runners, you can install all required security tools, logging agents, build tools, and packages that are needed to meet the <strong>organization\u2019s compliance requirements.<\/strong><\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">For production use cases, it is recommended to use self-hosted runners inside the organization\u2019s network. This helps avoid data exposure and allows secure access to internal resources.<\/div>\n<\/div>\n<h2 id=\"setup-prerequisites\">Setup Prerequisites<\/h2>\n<p>The following are the requirements to setup a self hosted runner.<\/p>\n<ol>\n<li>Azure DevOps account with required permissions<\/li>\n<li>Azure DevOps project with a pipeline.<\/li>\n<li>A virtual machine (e.g., <a href=\"https:\/\/devopscube.com\/reduce-github-actions-runner-cost\/\" rel=\"noreferrer\">AWS EC2<\/a>, Azure VM, etc)<\/li>\n<\/ol>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udccc<\/div>\n<div class=\"kg-callout-text\">In this setup we are using an Azure Ubuntu 24.04 VM as the runner.<\/div>\n<\/div>\n<h2 id=\"setting-up-an-azure-devops-self-hosted-runner\">Setting Up an Azure DevOps Self-Hosted Runner<\/h2>\n<p>Here, we are going to use a virtual machine as a runner.<\/p>\n<p>Assuming you already have an Azure VM ready.<\/p>\n<p>Now, we can start with the Personal Token creation.<\/p>\n<h3 id=\"step-1-generate-a-personal-access-token-pat\">Step 1: Generate a Personal Access Token (PAT)<\/h3>\n<p>The runner needs a Personal Access Token (PAT) to authenticate with Azure DevOps.<\/p>\n<p>To create a PAT, open <strong>Azure DevOps<\/strong> &#8211;&gt; login to the Azure DevOps <strong>organization<\/strong> &#8211;&gt; Select <strong>Project<\/strong>.<\/p>\n<p>On the project console, click the user icon on the right side corner and select the &#8220;<strong>User settings<\/strong>&#8220;<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-195.png\" class=\"kg-image\" alt=\"selecting azure devops project user setting to create a PAT\" loading=\"lazy\" width=\"1264\" height=\"573\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/10\/image-195.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/10\/image-195.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/10\/image-195.png 1264w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Select the &#8220;<strong>Personal access tokens<\/strong>&#8221; option to create a new one.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-183.png\" class=\"kg-image\" alt=\"selecting pat of azure devops project\" loading=\"lazy\" width=\"503\" height=\"433\"><\/figure>\n<p>Click the &#8220;<strong>+ New Token<\/strong>&#8221; button.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-184.png\" class=\"kg-image\" alt=\"creating a new pat for the self hosted runner\" loading=\"lazy\" width=\"367\" height=\"218\"><\/figure>\n<p>On the creation section, you need to provide a name for the PAT, select the organization, expiration date, and the permission level.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Here, in the scope, I am giving full access, but in real projects, select &#8220;<b><strong style=\"white-space: pre-wrap;\">Custom defined<\/strong><\/b>&#8221; and only give the least privileges.<\/div>\n<\/div>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-185.png\" class=\"kg-image\" alt=\"setting permission for the pat\" loading=\"lazy\" width=\"704\" height=\"407\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-185.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-185.png 704w\"><\/figure>\n<p>Once the creation is completed, you will see a success message and have a warning to save the generated PAT.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-186.png\" class=\"kg-image\" alt=\"the generated pat for the azure devops self hosted runner\" loading=\"lazy\" width=\"526\" height=\"330\"><\/figure>\n<p>By default, the PAT will not be stored anywhere, so before closing the window, store the PAT.<\/p>\n<p>In the next section, we will configure the runner.<\/p>\n<h3 id=\"step-2-create-an-agent-pool-on-the-azure-devops-project\">Step 2: Create an Agent Pool on the Azure DevOps Project.<\/h3>\n<p>To configure the project-level runner, go to <strong>project settings<\/strong> and select the &#8220;Agent pools&#8221; then click the &#8220;Add pool&#8221; button.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-188.png\" class=\"kg-image\" alt=\"the pool creation for the azure devops self hosted runner\" loading=\"lazy\" width=\"1144\" height=\"423\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-188.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/08\/image-188.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-188.png 1144w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>On the agent pool form, select the pool link as &#8220;New&#8221; and select the pool type as &#8220;Self-hosted&#8221;, give a name, and select the permission, then click the &#8220;Create&#8221; button to create the new pool.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-189.png\" class=\"kg-image\" alt=\"filling the pool creation form for the azure devosp pipeline\" loading=\"lazy\" width=\"528\" height=\"797\"><\/figure>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">If you are not sure about the pipeline workload, you can choose the &#8220;<b><strong style=\"white-space: pre-wrap;\">Azure virtual machine scale set<\/strong><\/b>&#8220;. So that the number of VMs will automatically scale up and down based on the workload.<\/div>\n<\/div>\n<p>Select the created pool to add the agents.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-190.png\" class=\"kg-image\" alt=\"selecting the created pool to add the agents\" loading=\"lazy\" width=\"824\" height=\"333\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-190.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-190.png 824w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">We can even create an organization-level runner in Azure DevOps from the Organization settings.<\/p>\n<p>These runners can be used for all projects across the organization.<\/p><\/div>\n<\/div>\n<h3 id=\"step-3-create-agent-in-azure-devops-project\">Step 3: Create Agent in Azure DevOps Project<\/h3>\n<p>Once open the pool, click the &#8220;<strong>New agent<\/strong>&#8221; to start the configuration.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-191.png\" class=\"kg-image\" alt=\"creating agent inside the pool\" loading=\"lazy\" width=\"463\" height=\"198\"><\/figure>\n<p>The next page will give you the instructions and commands to run on the server to turn it into a runner.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-195.png\" class=\"kg-image\" alt=\"the instructions and command to change the vm as runner\" loading=\"lazy\" width=\"1712\" height=\"1590\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-195.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/08\/image-195.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/08\/image-195.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-195.png 1712w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Here, I have selected the Linux OS because, we are going to use the Ubuntu server.<\/p>\n<p>Before we execute the commands on the virtual machine, we need the following prerequisites.<\/p>\n<ol>\n<li>Server URL &#8211; E.g., <code>https:\/\/dev.azure.com\/&lt;ORGANIZATION NAME&gt;<\/code><\/li>\n<li>PAT<\/li>\n<li>Agent Pool name<\/li>\n<\/ol>\n<p>Now, <a href=\"https:\/\/devopscube.com\/generate-ssh-key-pair\/\" rel=\"noreferrer\">SSH<\/a> to the virtual Machine.<\/p>\n<p>Create a directory<\/p>\n<pre><code class=\"language-bash\">mkdir myagent &amp;&amp; cd myagent<\/code><\/pre>\n<p>Download the runner setup package<\/p>\n<pre><code class=\"language-bash\">wget https:\/\/download.agent.dev.azure.com\/agent\/4.261.0\/vsts-agent-linux-x64-4.261.0.tar.gz<\/code><\/pre>\n<p>Extract the package<\/p>\n<pre><code class=\"language-bash\">tar zxvf vsts-agent-linux-x64-4.261.0.tar.gz<\/code><\/pre>\n<p>Configure the agent<\/p>\n<pre><code class=\"language-bash\">.\/config.sh<\/code><\/pre>\n<p>Once you reach the <code>.\/config<\/code> step, it will prompt you to provide the following.<\/p>\n<ol>\n<li>License agreement &#8211; Type <code>y<\/code><\/li>\n<li>Server URL &#8211; <code>https:\/\/dev.azure.com\/&lt;ORG NAME&gt;<\/code><\/li>\n<li>Authentication type &#8211; Press <code>Enter<\/code> for PAT and paste the PAT<\/li>\n<li>Agent pool name <\/li>\n<li>Name for the Agent<\/li>\n<\/ol>\n<pre><code class=\"language-bash\">azureuser@azure-runner-vm:~\/myagent$ .\/config.sh\n\n  ___                      ______ _            _ _\n \/ _ \\                     | ___ (_)          | (_)\n\/ \/_\\ \\_____   _ _ __ ___  | |_\/ \/_ _ __   ___| |_ _ __   ___  ___\n|  _  |_  \/ | | | '__\/ _ \\ |  __\/| | '_ \\ \/ _ \\ | | '_ \\ \/ _ \\\/ __|\n| | | |\/ \/| |_| | | |  __\/ | |   | | |_) |  __\/ | | | | |  __\/\\__ \\\n\\_| |_\/___|\\__,_|_|  \\___| \\_|   |_| .__\/ \\___|_|_|_| |_|\\___||___\/\n                                   | |\n        agent v4.261.0             |_|          (commit 45f3f01)\n\n\n&gt;&gt; End User License Agreements:\n\nBuilding sources from a TFVC repository requires accepting the Team Explorer Everywhere End User License Agreement. This step is not required for building sources from Git repositories.\n\nA copy of the Team Explorer Everywhere license agreement can be found at:\n  \/home\/azureuser\/myagent\/license.html\n\nEnter (Y\/N) Accept the Team Explorer Everywhere license agreement now? (press enter for N) &gt; y\n\n&gt;&gt; Connect:\n\nEnter server URL &gt; https:\/\/dev.azure.com\/devopscube-org\nEnter authentication type (press enter for PAT) &gt; \nEnter personal access token &gt; ************************************************************************************\nConnecting to server ...\n\n&gt;&gt; Register Agent:\n\nEnter agent pool (press enter for default) &gt; custom-linux-pool\nEnter agent name (press enter for azure-runner-vm) &gt; linux-agent\nEnter replace? (Y\/N) (press enter for N) &gt; n\nScanning for tool capabilities.\nConnecting to the server.\nSuccessfully added the agent\nTesting agent connection.\nEnter work folder (press enter for _work) &gt; \n2025-10-22 09:51:04Z: Settings Saved.<\/code><\/pre>\n<p>The configurations are ready now, so we need to make them run the agent.<\/p>\n<pre><code class=\"language-bash\">.\/run.sh<\/code><\/pre>\n<p>You will see the following output.<\/p>\n<pre><code class=\"language-bash\">$ .\/run.sh\n\nScanning for tool capabilities.\nConnecting to the server.\n2025-10-22 09:58:14Z: Listening for Jobs<\/code><\/pre>\n<p>This output ensures that the agent is configured and running. We can confirm this from the dashboard.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-196.png\" class=\"kg-image\" alt=\"the confirmation of the agent creation and the status from the console\" loading=\"lazy\" width=\"889\" height=\"288\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-196.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-196.png 889w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The console output also shows that the runner is active. So we can test now.<\/p>\n<h2 id=\"testing-the-self-hosted-azure-devops-runner\">Testing the Self-Hosted Azure DevOps Runner<\/h2>\n<p>To test the self-hosted runner, we need to call it from a pipeline.<\/p>\n<p>We create a fresh test pipeline. For that, navigate to <strong>Pipelines &#8211;&gt; New pipeline &#8211;&gt; Azure Repos Git &#8211;&gt; [Select Repository] &#8211;&gt; Starter pipeline<\/strong>.<\/p>\n<p>Now, you can see a YAML file with some default configurations. Replace the entire contents with the following.<\/p>\n<pre><code class=\"language-yaml\">trigger:\n- main\n\npool:\n  name: 'custom-linux-pool'\n  demands:\n  - Agent.Name -equals linux-agent\n\nsteps:\n- script: whoami\n  displayName: 'Check current user'\n\n<\/code><\/pre>\n<p>Here,<\/p>\n<ul>\n<li><code>custom-linux-pool<\/code> is the custom pool name.<\/li>\n<li><code>linux-agent<\/code> is the agent name inside the pool.<\/li>\n<\/ul>\n<p>Then click &#8220;<strong>Save and run<\/strong>&#8221; and the pipeline will automatically start running.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-40.png\" class=\"kg-image\" alt=\"the pipeline running status with the self hosted runner.\" loading=\"lazy\" width=\"956\" height=\"620\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/09\/image-40.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-40.png 956w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Once you select the specific run, we can see the summary.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-41.png\" class=\"kg-image\" alt=\"the list of pipeline runs in azure devops\" loading=\"lazy\" width=\"1186\" height=\"557\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/09\/image-41.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/09\/image-41.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-41.png 1186w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Once you select the specific job, we can see the logs.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-42.png\" class=\"kg-image\" alt=\"the log of the specific pipeline job execution\" loading=\"lazy\" width=\"828\" height=\"623\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/09\/image-42.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-42.png 828w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Select the particular step to see the execution of the task.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-43.png\" class=\"kg-image\" alt=\"the log of the specific task execution\" loading=\"lazy\" width=\"1132\" height=\"522\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/09\/image-43.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/09\/image-43.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/09\/image-43.png 1132w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The output clearly shows that the jobs are run inside the self-hosted runner.<\/p>\n<p>Now, you can install the required tools inside the VM so that you can use it for your real pipeline.<\/p>\n<p>In the next section, we can see some of the best practices of the self-hosted runner.<\/p>\n<h2 id=\"best-practices\">Best Practices<\/h2>\n<p>Here are some of the best practices that you can follow<\/p>\n<ol>\n<li>Instead of using a single runner for each project, use dedicated runners to improve security.<\/li>\n<li>Set proper expiry and rotation for the PAT and give only necessary permissions to avoid any security threats.<\/li>\n<li>Create <a href=\"https:\/\/devopscube.com\/packer-tutorial-for-beginners\/\" rel=\"noreferrer\">golden images using Packer <\/a>so that when a new project requires a runner, we can easily spin up with all necessary tools.<\/li>\n<li>For production setup, using private instances as runners can increase security.<\/li>\n<li>Choosing the right size instance is very important to avoid any performance issues.<\/li>\n<li>Choosing the spot VMs can reduce the cost.<\/li>\n<\/ol>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>In this guide, I showed you how to setup a virtual machine as a runner for the <a href=\"https:\/\/devopscube.com\/learning-ci-cd-tools\/\" rel=\"noreferrer\">CI\/CD<\/a> pipelines in Azure.<\/p>\n<p>Self-host runners will give more flexibility than the built-in VMs, though choosing the right instances and following all the best practices only gives security and speed.<\/p>\n<p>If you face any errors during the setup, let me know in the comments.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/setup-self-hosted-runner-azure-devops-pipeline\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Setup Self Hosted Linux Runner for Azure DevOps? \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/setup-self-hosted-runner-azure-devops-pipeline\/<\/p>\n","protected":false},"author":1,"featured_media":369,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-368","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/368","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=368"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/368\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/369"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=368"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=368"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=368"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}