As you probably know, when queuing builds (Azure DevOps pipelines) on Azure DevOps, the build is delegated to the first available build agent in agent pool. These build agents can be Microsoft Hosted (Azure DevOps) or Self-hosted. First ones are cover majority of build scenarios, they are already prefabricated and ready to use in Azure DevOps. The latter ones are custom build agents and can be used if there are some special software needs (e.g. license, special software of some custom configurations, restricted on-premise resources, etc…) or special hardware or any other requirements.
If you read my blog post How to setup private Linux Azure DevOps Agent or How to setup private Mac Azure DevOps Agent you already know what I am talking/writing about. In those two blog posts I presented how to setup Linux or MacOs build agent on-premises. I quickly showed how to compile simple c code on a GCC compiler. You can check the demo code which I used for those two blog posts (and for this one also) code here: https://github.com/josipx/Jenx.LinuxAzureDevOps.Demo.
Similarly, I will show the same problem-solution in this blog post, but this time with Docker container. First of all, let’s check some documentation.
Documentation
Microsoft has awesome documentation and instructions how to host Linux Azure DevOps agent in Docker container is no exception. Thus, I just follow instructions in this link: https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/docker?view=azure-devops#linux.
Let’s dig in
For this purpose, I will create custom Docker image from custom Dockerfile
file. Therefore, I create empty folder with two files inside: Dockerfile
and start.sh
and copy following code (follow this link for more information).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
FROM ubuntu:16.04 # To make it easier for build and release pipelines to run apt-get, # configure apt to not require confirmation (assume the -y argument by default) ENV DEBIAN_FRONTEND=noninteractive RUN echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ jq \ git \ iputils-ping \ libcurl3 \ libicu55 \ libunwind8 \ netcat \ gcc \ libc6-dev WORKDIR /azp COPY ./start.sh . RUN chmod +x start.sh CMD ["./start.sh"] |
This Dockerfile
is basically the same as in official instructions, except I added two lines, i.e. lines 19 and 20. In my case, I need GCC compiler on my docker image to compile my awesome C application hosted on GitHub: https://github.com/josipx/Jenx.LinuxAzureDevOps.Demo.
start.sh
script is the same as in official documentation. Just for consistency, I will copy content of this file here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
#!/bin/bash set -e if [ -z "$AZP_URL" ]; then echo 1>&2 "error: missing AZP_URL environment variable" exit 1 fi if [ -z "$AZP_TOKEN_FILE" ]; then if [ -z "$AZP_TOKEN" ]; then echo 1>&2 "error: missing AZP_TOKEN environment variable" exit 1 fi AZP_TOKEN_FILE=/azp/.token echo -n $AZP_TOKEN > "$AZP_TOKEN_FILE" fi unset AZP_TOKEN if [ -n "$AZP_WORK" ]; then mkdir -p "$AZP_WORK" fi rm -rf /azp/agent mkdir /azp/agent cd /azp/agent export AGENT_ALLOW_RUNASROOT="1" cleanup() { if [ -e config.sh ]; then print_header "Cleanup. Removing Azure Pipelines agent..." ./config.sh remove --unattended \ --auth PAT \ --token $(cat "$AZP_TOKEN_FILE") fi } print_header() { lightcyan='\033[1;36m' nocolor='\033[0m' echo -e "${lightcyan}$1${nocolor}" } # Let the agent ignore the token env variables export VSO_AGENT_IGNORE=AZP_TOKEN,AZP_TOKEN_FILE print_header "1. Determining matching Azure Pipelines agent..." AZP_AGENT_RESPONSE=$(curl -LsS \ -u user:$(cat "$AZP_TOKEN_FILE") \ -H 'Accept:application/json;api-version=3.0-preview' \ "$AZP_URL/_apis/distributedtask/packages/agent?platform=linux-x64") if echo "$AZP_AGENT_RESPONSE" | jq . >/dev/null 2>&1; then AZP_AGENTPACKAGE_URL=$(echo "$AZP_AGENT_RESPONSE" \ | jq -r '.value | map([.version.major,.version.minor,.version.patch,.downloadUrl]) | sort | .[length-1] | .[3]') fi if [ -z "$AZP_AGENTPACKAGE_URL" -o "$AZP_AGENTPACKAGE_URL" == "null" ]; then echo 1>&2 "error: could not determine a matching Azure Pipelines agent - check that account '$AZP_URL' is correct and the token is valid for that account" exit 1 fi print_header "2. Downloading and installing Azure Pipelines agent..." curl -LsS $AZP_AGENTPACKAGE_URL | tar -xz & wait $! source ./env.sh trap 'cleanup; exit 130' INT trap 'cleanup; exit 143' TERM print_header "3. Configuring Azure Pipelines agent..." ./config.sh --unattended \ --agent "${AZP_AGENT_NAME:-$(hostname)}" \ --url "$AZP_URL" \ --auth PAT \ --token $(cat "$AZP_TOKEN_FILE") \ --pool "${AZP_POOL:-Default}" \ --work "${AZP_WORK:-_work}" \ --replace \ --acceptTeeEula & wait $! print_header "4. Running Azure Pipelines agent..." # `exec` the node runtime so it's aware of TERM and INT signals # AgentService.js understands how to handle agent self-update and restart exec ./externals/node/bin/node ./bin/AgentService.js interactive |
This script handles Docker container startup/bootstrap sequence. It handles input parameters on docker run
command and all related startup tasks to start internal Azure DevOps agent.
Finally, when these two files are on place, I build docker container from Dockerfile
and run container with correct parameters.
Building Linux Azure DevOps agent Docker image
I my console I hit this command in order to build docker image from Dockerfile:
1 |
docker build -t linux-azure-dev-ops-agent:latest . |
As a result, on my local Docker I have newly created image!
Get Azure DevOps ready to work with agent
Before starting of the my build agent docker container, I just need a few additional information from Azure DevOps, like:
- Url to Azure DevOps root <AZP_URL>
- PAT (Personal Access Token) <AZP_TOKEN>
- Azure DevOps Pool name <AZP_POOL>
Let’s quickly show how can I get all these parameters. When I log into Azure DevOps portal first I select my project. This URL is the AZP_URL parameter.
Agent Pools and Agents are listed in Azure DevOps Settings page. I get information for AZP_POOL parameter here.
Personal Access Tokens (AZP_TOKEN parameter) can be accessed/created in User Personal Profile page:
After I have all startup parameters, I can start Docker container. I do this by running:
1 |
docker run -e AZP_URL=https://dev.azure.com/jenxsoft -e AZP_TOKEN=<token> -e AZP_AGENT_NAME=docker-linux-agent -e AZP_POOL=AzureHostedLinuxAgentPool linux-azure-dev-ops-agent:latest |
In my Azure DevOps agent listing, I have new build agent registered and ready to use, as seen below:
So, my On-Premise-Docker-hosted agent is up, running and registered to Azure DevOps. Let’s test it by building my demo application. I select my testing build pipeline.
Pipeline tasks/sequence are: first, I get code from GitHub, compile it with GCC compiler and then I copy binaries to Artifacts repository. To build my app, I just need to Queue pipeline and Azure DevOps services will use first available agent (my Docker registered agent, because it’s the only active one in this pool) to build my app.
My build went OK, so my output is all green:
I have binaries collected in my Artifact repository.
If I run this binary on my Linux machine, I have this output.
All good!
Conclusion
Docker containerization is relatively simple and efficient way to run applications. In this blog post I presented how to run Linux Azure Build agent in Docker container. I also presented how to run and to integrate agent into Azure DevOps. A demo app is also pulled from GitHub, compiled and binaries pushed into Azure Artifacts folder. This blog post and demo contains all crucial parts of simple building of application hosted on GitHub and build on on-premise build system hosted in Docker container.
Happy coding!
2 thoughts on “Running a self-hosted Azure DevOps Linux agent in Docker”
Thank you for sharing this article, my agent is running only one job, after that, it went idle o idle state and not picking any jobs.
Hello, you did build a great article here have to say, I have a small issue with this VSTS agent containerization in Linux VM, I have written a Stackoverflow post with all the details that I’ll like to share with you in case you could help me with this, thanks in advance