Image 2
View All Posts

Running GitLab Runner in Azure: Container Apps, App Service, or VM?

This article shows how to securely and reliably run GitLab Runner in Azure and compares Azure Container Apps, App Service, and virtual machines as hosting options.

Microsoft Azure
Cloud Architecture
CI/CD
Platform Engineering
Image

Why Shared Runners Are Often Not Enough

As soon as pipelines need to access private Azure resources such as Key Vaults with Private Endpoints, internal Azure Kubernetes Service clusters, or Azure Container Registry instances without public access, shared runners quickly become a bottleneck.

In this blog post, I want to outline the available options for hosting GitLab Runner in Azure. This topic has come up repeatedly in our project engagements, as GitLab’s capabilities often lead to increased use of CI/CD automation and deeper integration with Azure services. Therefore, this article explains the available options and highlights their respective advantages and disadvantages.

To gain full control over network paths, runtime environments, and identities, a self-hosted runner is the ideal solution to meet these requirements.

Self-hosted runners in Azure address exactly these three areas:

  • Runners operate within your own tenant and subscription.
  • Network access follows your landing zone architecture.
  • Azure access is handled through Managed Identity instead of static secrets.

The key question is not whether to use self-hosted runners, but rather which hosting model best fits your workloads from an operational and security perspective.

Decision Logic Instead of Feature Lists

To simplify decision-making, I use the following questions in real-world projects:

Question 1: Is isolation required per job?

→ If yes: Prefer container-based solutions to scale the number of runners dynamically as needed.

Question 2: Do builds require privileged Docker operations or high I/O performance?

→ If yes: Use VM-based GitLab runners, for example to build applications, container images, or other system-level artifacts.

Question 3: How much does the workload fluctuate throughout the day?

→ In cases of high volatility: Use auto-scaling rules with VMs or Container Apps. Scaling with App Service is unfortunately not very intuitive or flexible in Azure.

Question 4: Who operates and maintains the GitLab runners long-term?

→ Small platform team: Prefer managed runtimes such as Azure Container Apps or App Service. Maintaining hardened container images is typically easier and more manageable than maintaining virtual machines.

In practice, this usually leads to three valid options in Azure: Container Apps, App Service, and VM/VM Scale Sets. I intentionally excluded other hosting approaches because, for container-based workloads, Azure-managed solutions are generally the preferred choice.

Azure Container Apps are the best starting point in many enterprise environments. They offer low operational overhead, built-in VNet integration, and clear scaling mechanics to quickly adjust the number of instances as demand increases.

When it fits well

  • Standardized container builds and test jobs
  • Variable workloads, such as high CI load during the day and low usage at night
  • Platform teams that want to simplify operations

Technical key aspects

  • Access to Azure resources via user-assigned or system-assigned Managed Identity
  • Secrets such as runner tokens or registry credentials referenced from Key Vault instead of being stored in plaintext
  • Use of custom images to further harden the default GitLab Runner images

Production implications

  • Network: Use a dedicated subnet for Container Apps and define UDR and firewall rules early.
  • Security: Strictly limit privileged containers. If Docker-in-Docker is required, constrain its scope carefully.
  • Observability: Use Log Analytics and metrics to monitor queue times, job failure rates, and startup latency.
  • Lifecycle: Version runner images explicitly instead of relying on latest.

Terraform example

resource "azurerm_container_app" "runner" {
  name                         = "ca-gitlab-runner-${var.env}"
  resource_group_name          = var.rg_name
  container_app_environment_id = var.cae_id
  revision_mode                = "Single"
 
  identity {
    type = "UserAssigned"
    identity_ids = [var.runner_uami_id]
  }
 
  secret {
    name  = "runner-auth-token"
    value = var.runner_auth_token
  }
 
  template {
    min_replicas = 1
    max_replicas = 10
 
    container {
      name   = "runner"
      image  = var.runner_image
      cpu    = 1.0
      memory = "2Gi"
 
      env {
        name  = "CI_SERVER_URL"
        value = var.gitlab_url
      }
 
      env {
        name        = "RUNNER_TOKEN"
        secret_name = "runner-auth-token"
      }
    }
  }
}

Option B: Azure App Service (Only for Clearly Defined Scenarios)

Azure App Service may seem attractive at first, but for runner workloads it is often a compromise. It can work well for simple, continuously running workloads, but it is typically too rigid for heterogeneous CI/CD workloads. This is mainly due to the lack of flexible container scaling capabilities. In most cases, there is effectively only a single runner instance.

When it fits well

  • Small teams with consistently low workload
  • Limited need for deep runtime control
  • Focus on getting something stable online quickly rather than maximizing flexibility

Typical limitations in practice

  • No meaningful scale-to-zero capability for runner scenarios
  • Docker-specific use cases (privileged tasks or nested workloads) are difficult to implement
  • Less operational and debugging flexibility compared to Virtual Machines or VM Scale Sets

If App Service is already the established compute standard in your organization, it can serve as a transitional solution. However, it is rarely a strong strategic target platform for runners due to its limited flexibility.

Option C: Azure VM / VM Scale Set (For Heavy or Specialized Workloads)

Virtual machines are the right choice when pipelines need to operate close to the operating system. This includes large Docker builds, specialized kernel or tooling requirements, high disk I/O workloads, or complex caching strategies.

When it fits well

  • Build jobs with high CPU, RAM, or disk I/O requirements
  • Shell executors or heavily customized runner setups
  • Need for deep host-level diagnostics and control

Typical operational reality

  • Patch and vulnerability management is your responsibility for the entire virtual machine
  • Autoscaling with VM Scale Sets requires properly defined scaling rules and a health model
  • Without consistent hardening, the attack surface increases quickly

A minimal hardening baseline should include

  • SSH access only via Bastion or Just-in-Time (JIT), with no publicly exposed management ports
  • OS hardening, Microsoft Defender for Cloud, and regular image rotation
  • Running the runner as an unprivileged service user
  • Restrictive egress control (explicitly allow only ACR, GitLab, or required Azure APIs)

Architecture Comparison

CriteriaContainer AppsApp ServiceVM/VMSS
Operational overheadLowLow to mediumHigh
Runtime controlMediumLowVery high
Suitability for volatile workloadsHighMediumMedium
Suitability for heavy buildsMediumLowHigh
Security hardening depthMediumMediumVery high (but requires own effort)

Practical Recommendation

For most teams, Azure Container Apps are the recommended default choice. For heavy or system-level builds, virtual machines or VM Scale Sets are the better option. Azure App Service should only be used if it is already established organizationally and the requirements are simple.

More important than the platform choice itself is to properly design three foundational areas early on:

1. Identity model: Managed Identity, Key Vault, and avoiding secret sprawl.

2. Network model: Private Endpoints, controlled egress, and reliable DNS resolution.

3. Operational model: Monitoring, image lifecycle management, and incident runbooks.

When these three foundations are in place, the runner stops being an operational burden and becomes a stable and reliable part of your platform.


Review and Secure Your Azure and CI/CD Platform

Self-hosted GitLab runners are a critical part of modern cloud platforms, but risks often emerge in identity configuration, network paths, and operational design.

With our Cloud Audit, we analyze your Azure and CI/CD environment in a structured way and identify concrete improvements across security, architecture, and operational resilience.

You receive clear recommendations, prioritized actions, and a solid foundation to make your platform secure, scalable, and production-ready.

https://henden-consulting.de/de/cloud-audit


Interested in Working Together?

We look forward to hearing from you.

Don't like forms?

mertkan@henden-consulting.de