At my current employer we are still using a local build server to host our Azure Pipeline agents and run our builds. Having multiple agents running on the same machine works most of the time as most frameworks and libraries we depend on allow multiple side-by-side installations. Unfortunately there is one framework that doesn't like this; node.js. So far, we have worked around this by using NVM(Node Version Manager) to switch between node.js version. Of course this only works as long as no 2 builds are running at the same time that use a different node.js version.
We did a previous attempt to fix this problem by running docker on our build server and host separate build agents in a container. But it introduced too much overhead on our build server and we never succeeded in getting it stable.
As we had to move our build environment to a new server, we thought it would be a good time to finally fix this problem; this time by running multiple Linux distributions using WSL2 instead of a docker image.
Remark: Having a docker image would still be beneficial as it would help us keep the environments in sync and automate the installation of all the dependencies.
Prerequisites
Before we begin, ensure you have the following prerequisites in place:
- Windows Server 2022 or higher
- WSL2 installed and configured
- A Linux distribution installed in WSL2 (Ubuntu 20.04 or 22.04 recommended)
- An Azure DevOps organization with appropriate permissions to create agent pools
- Basic familiarity with Linux command line operations
Setting up WSL2 on the server
If you haven't already set up WSL2, here's a quick overview of the process. Open PowerShell as Administrator and run:
wsl --install
This command installs WSL2 with Ubuntu as the default distribution. After installation, restart the server.
On our build server, WSL was already installed and I got the following message when trying to run this command:
PS C:\Users\bawu> wsl --install
Windows Subsystem for Linux is already installed.
The following is a list of valid distributions that can be installed.
Install using 'wsl --install -d <Distro>'.
NAME FRIENDLY NAME
Ubuntu Ubuntu
Debian Debian GNU/Linux
kali-linux Kali Linux Rolling
Ubuntu-18.04 Ubuntu 18.04 LTS
Ubuntu-20.04 Ubuntu 20.04 LTS
Ubuntu-22.04 Ubuntu 22.04 LTS
Ubuntu-24.04 Ubuntu 24.04 LTS
OracleLinux_7_9 Oracle Linux 7.9
OracleLinux_8_10 Oracle Linux 8.10
OracleLinux_9_5 Oracle Linux 9.5
openSUSE-Leap-15.6 openSUSE Leap 15.6
SUSE-Linux-Enterprise-15-SP6 SUSE Linux Enterprise 15 SP6
openSUSE-Tumbleweed openSUSE Tumbleweed
I tried to run the command with a specific distro. However this failed with the error message below:
PS C:\Users\bawu> wsl --install -d Ubuntu
Installing: Ubuntu
An error occurred during installation. Distribution Name: 'Ubuntu' Error Code: 0x8000ffff
I was able to fix it by first running an update:
PS C:\Users\bawu> wsl --update
Downloading: Windows Subsystem for Linux
Installing: Windows Subsystem for Linux
Windows Subsystem for Linux has been installed.
PS C:\Users\bawu> wsl --install -d Ubuntu
WSL is finishing an upgrade...
Installing Windows optional component: VirtualMachinePlatform
Deployment Image Servicing and Management tool
Version: 10.0.20348.2849
Image Version: 10.0.20348.3932
Enabling feature(s)
[==========================100.0%==========================]
The operation completed successfully.
The requested operation is successful. Changes will not be effective until the system is rebooted.
Installing the Linux agent in WSL2
Now let's install and configure the Azure Pipelines agent within WSL2. Start by updating your WSL2 system packages:
sudo apt update && sudo apt upgrade -y
Create a dedicated directory for the agent and navigate to it:
mkdir ~/agent && cd ~/agent
Download the latest agent package. You can find the current download URL in your Azure DevOps organization under Project Settings > Agent pools > Download agent:
wget https://download.agent.dev.azure.com/agent/3.238.0/vsts-agent-linux-x64-3.238.0.tar.gz
Extract the agent package:
tar zxvf vsts-agent-linux-x64-3.238.0.tar.gz
Configuring the agent
Before configuring the agent, you'll need to create a Personal Access Token (PAT) in Azure DevOps. Navigate to your Azure DevOps organization, click on your profile picture, select "Personal access tokens," and create a new token with "Agent Pools (read, manage)" permissions.
Run the configuration script:
./config.sh
The configuration script will prompt you for several pieces of information:
- Server URL: Your Azure DevOps organization URL (e.g., https://servername/tfs/defaultcollection)
- Remark: Specify the server URL including the collection as the generated PAT will always be scoped at the collection level. Otherwise you will get an VS30063 error message.
- Authentication type: Choose PAT (Personal Access Token)
- Personal access token: Enter the PAT you created earlier
- Agent pool: Choose an existing pool or create a new one (default is usually fine)
- Agent name: Give your agent a descriptive name (e.g., "MrBlue", we are Reservoir Dogs fans)
- Work folder: Accept the default or specify a custom path
The configuration process will register your agent with Azure DevOps and prepare it for running builds.
Now is also a good time to install any other dependencies you need on this WSL2 instance:
First add the Microsoft Package reference repository:
wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
Afterwards you can install the SDK on the agent:
sudo apt-get update && \ sudo apt-get install -y dotnet-sdk-9.0
Running the agent
Once the configuration process has completed, we can run the agent:
./run.sh
The agent will scan for available tool capabilities and after that it should appear in your configured pool:
Right now, our agent is running in interactive mode. This is good to test if everything works, but requires us to have an active user session.
To have the agent run unattended, we add a scheduled task to our Windows Server. This task will start a WSL2 distro on server startup and run the agent as a service.
Create a new Scheduled task
Set the Security options to Run whether user is logged in or not.
Go to Triggers and create a new Trigger. Set Begin the task to At startup.
On the Actions tab, create a new Action.
Set the Action to Start a program
Set the Program/Script to
wsl
>Set the arguments to
-d Ubuntu /home/<serviceaccount>/agent/run.sh
Now your build agent should start automatically when the server boots up.
Create a build pipeline that targets our Linux agent
If you want to create a pipeline that should run on the Linux agent, you can specify a demand with the Agent.OS set to Linux:
Optimizing performance
To get the best performance from your WSL2 agent, consider these optimizations:
Memory Configuration: Create or modify /etc/wsl.conf
in your WSL2 distribution to allocate appropriate resources:
[wsl2]
memory=8GB
processors=4
swap=2GB
File System Performance: Store your source code and build artifacts on the Linux file system rather than accessing Windows drives through /mnt/c/
. This provides significantly better I/O performance.
More information
WSL 2 distros are now supported on Windows Server - Windows Command Line
Install Linux Subsystem on Windows Server | Microsoft Learn
Switch between node versions on Windows