Learning to set up Azure Private Networking for GitHub-hosted runners. Based on this guide with some personal preferences with regards to using PowerShell and Bicep.
Why? You can use GitHub-hosted runners in an Azure VNET. This enables you to use GitHub-managed infrastructure for CI/CD while providing you with full control over the networking policies of your runners. See more details in the documentation.
Tip
I am currently re-working this demo with support for new GitHub API's allowing for full end-to-end automated deployment. You can check out the previous (still functional!) version see v1 here. (Run git checkout v1
after cloning.)
- An Azure subscription with Contributor and Network Contributor permissions (least privilege) or Owner permissions
- An Enterprise Cloud GitHub organization with CI/CD Admin (least privilege) or organization Owner
- GitHub CLI (tested with 2.67)
- PowerShell 7.x with Azure PowerShell modules (tested with Az.Resources 7.8.1)
- Azure Bicep (tested with 0.33.93)
Note that there is limited support for Azure Regions. See supported regions here.
-
Authenticate to GitHub CLI by running
gh auth login
- TODO: correct token usage or scope
-
Find your organization id by running the following script and providing the username of your GitHub organization:
./scripts/gh-api-prereqs.ps1 -OrganizationUsername <org-username>
# Output:
{
"data": {
"organization": {
"login": "<org-username>",
"databaseId": <id>
}
}
}
👉 Copy the value from the "databaseId"
field for the next step.
- Deploy a subnet
Option 1: Sandbox deployment: Run the following deployment script to create a new resource group, a new virtual network and configure a new subnet to be set up for private networking:
./scripts/deploy.ps1 -GitHubDatabaseId <databaseId>
# Output
Registring GitHub.Network resource provider...
Configuring resource group and virtual network...
Deploying template...
✅ Deployment complete!
Network Settings Resource Id:
<network settings resource id>
👉 Copy the Network Settings Resource Id
value for the next step.
Option 2: Deploy to existing vnet: If you want to set up a new subnet in an existing virtual network you can deploy the main.bicep
and provide the necessary parameters by editing the main.bicepparam
file, and then running the following command:
$resourceGroupName = "<existing resource group name>"
$deploy = New-AzResourceGroupDeployment -Name "gh-private-runners-$now" `
-ResourceGroupName $resourceGroupName -TemplateFile './bicep/main.bicep' `
-TemplateParameterFile "./bicep/main.bicepparam"
$networkSettings = Get-AzResource -ResourceId $deploy.Outputs.networkSettingsId.value
Write-Host "Network Settings Resource Id:"
Write-Host $networkSettings.Tags['GitHubId']
👉 Copy the Network Settings Resource Id
value for the next step.
- Configure the network configuration for your organization in GitHub
See steps here. Remember to connect the runner to a runner group and configure labels accordingly.
- Use the new privately networked GitHub-hosted runner!
You should be able to use the runner by following the same steps as in:
As of Q1 2025 it's now possible to configure all of this programmatically!
See details about deleting the configuration here.
After completing clean-up in Azure you can also delete the resource group if you have deployed it as a sandbox.
If you are considering running runners for GitHub Actions in your own Azure private networking, and this scenario does not suit you, you can also consider:
- Running self-hosted runners on Azure Container App Jobs (simple and cost-effective solution)
- Running self-hosted runners on whatever compute and infrastructure you like (can be a hassle..)
Based on GitHub documentation it's recommended to add a 30% buffer to the maximum job concurrency you anticipate. This needs to be taken into account when choosing the subnet size (Azure) and the maximum count of runners (GitHub) in the setup. Note that Azure reserves five of the IP addresses in a given subnet.
Subnet size | IP addresses | Usable IP addresses | Max recommended # of runners |
---|---|---|---|
/28 | 16 | 11 | 8 |
/27 | 32 | 27 | 20 |
/26 | 64 | 59 | 45 |
/25 | 128 | 123 | 94 |
/24 | 256 | 251 | 190 |
/23 | 512 | 507 | 390 |