Scalable Container Based Azure Pipelines Pools with Azure Container Apps

Sebastian SchützeSebastian Schütze

Update 22.02.2022

There is a thread about this approach regarding Container Apps only supporting ScaledObjects and not ScaledJobs. Which is needed to not cancel agent jobs when container apps are scaled down. So for now scaling is not really usable for ACI (Azure Container Apps).

See below

It has been removed from the current public documentation as well but there is a feature request to support scaled jobs in the future. So use this as a basic concept and stay tuned until scaled jobs really work with Azure Pipelines.


When Microsoft’s Ignite 2021 in November happened a new serverless container based service has been released. It allows more capabilities and control than container instances but is abstracted away from a full kubernetes cluster (like AKS in Azure).

Additionally, it is concentrating on horizontal scaling (with KEDA scalers, look on GitHub) and microservices best practices to make it easier for developers to handle multiple containers running side by side (with Dapr, look on GitHub).

Note: This blog post assumes that you can read docs (I put all the links) and you know your way around. I also provide all the code at the end of the blog post. That is why I am keeping it short here and explain only the new shit here. 😉

What is a KEDA Scaler?

KEDA scalers are actually listening to a defined metric for a resource that the scaler integrates and then horizontally scales a container template or a template for a group of containers working together (microservices).

And what is a container template?

I just called it like that, because the ARM API is defining it like that for the Container Apps. But basically, it is a blueprint which says, which docker images with what compute resource and which scaler it should run and scale.

KEDA scalers can scale with anything that is measurable like CPU and memory usage. Or Azure storage queues. Or even based on MSSQL query results. It is basically up to the user or someone who is implementing a scaler.

KEDA Azure Pipelines scaler

These two beasts, KEDA and Dapr, actually is a very good integration to make using and deploying container so easy it is unbelievable. Especially, one specific KEDA scaler is very interesting from Azure DevOps (and my) perspective: the Azure Pipelines scaler.

The scaler itself is written in Go and can be used to scale container based agents in one agent pool. This means you can spin up new container instances based on the queue length of waiting jobs in a pool.

There is one simple parameter to fine grain the scalability: How many jobs should be waiting before a new container is created?

Documentation says:

Example – If one pod can handle 10 jobs, set the queue length target to 10. If the actual number of jobs in the queue is 30, the scaler scales to 3 pods.

KEDA | Azure Pipelines

Azure Container Apps (Preview) and Azure Pipelines scaler

At the time I am writing this the service is in preview and maybe out for about three weeks.

Creating a container app is fairly easy either with the Azure portal or with the azure cli. My experience was that using an ARM template was the easiest approach (because any abstraction layer can only create what ARM supports…). But the documentation itself is (nature of previews) very, lets say spartan.

You find only scaling examples for CPU and memory. But I added a PR to improve the Azure docs for this scaler.

Let’s get to it. The scaler is defined as follow in an ARM template

"scale": {
    "minReplicas": "1",
    "maxReplicas": "5",
    "rules": [
            "name": "azdo-agent-scaled",
            "custom": {
                "type": "azure-pipelines",
                "metadata": {
                    "poolID": "[parameters('azp_poolId')]",
                    "targetPipelinesQueueLength": "1"
                "auth": [
                        "secretRef": "azp-token",
                        "triggerParameter": "personalAccessToken"
                        "secretRef": "azp-url",
                        "triggerParameter": "organizationURL"

I will give you just the format, because parameters like poolID, targetPipelinesQueueLength, personalAccessToken, organizationURL are explained on the KEDA docs pages. Look at the end of the article for my full code in my GitHub Repo.

Agent Container Image

There is not much to explain. Just so you don’t ask where it comes from. I got the base image from the docs. It is very basic and only good for this demonstration.

Use it. It gets the job done.

Additionally you want to save the image to an Azure Container Registry. You can also find documentation on how to create one and push your agent image.

Resources on Azure DevOps Services

Here is also not much needed. You need basically:

Full Code Sample

You can find a full code on GitHub for:


Limitations and wishes

So there are some limitations.

Also published on Medium.

Sebastian is an Azure Nerd with focus on DevOps and Azure DevOps (formerly VSTS) that converted from the big world of SharePoint and O365. He was working with O365 since 2013 and loved it ever since. As his focus shifted in 2017 to more DevOps related topics in the Microsoft Stack. He learned to love the possibilities of automation. Besides writing articles in his blog and German magazines, he is still contributing to the SharePoint Developer Community (and PnP SharePoint) to help to make the ALM part a smoother place to live in.

Comments 2

This site uses Akismet to reduce spam. Learn how your comment data is processed.