> ## Documentation Index
> Fetch the complete documentation index at: https://docs.macstadium.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Azure DevOps agents on Orka

> Run Azure Pipelines self-hosted macOS agents on Orka VMs: build an agent image, register agents to a pool, and target the pool from your YAML pipelines.

[Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/) can run macOS jobs on Orka VMs through Microsoft's self-hosted agent. There's no Orka plugin or extension for Azure DevOps. You install Microsoft's agent in an Orka image, register each VM as an agent in a pool, and point your pipelines at that pool, the same way you'd use any self-hosted agent.

This page covers the Orka side. For the agent itself, see Microsoft's [Self-hosted macOS agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent).

## How it works

* Each Orka VM runs one Azure Pipelines agent, registered in an agent pool in your Azure DevOps organization or collection.
* The agent connects out to Azure DevOps over HTTPS and asks for jobs. Azure DevOps never connects into your network, so you don't open any inbound ports.
* Jobs reach the agents through the pool name in your YAML. Your pipelines, logs, and the Azure DevOps UI don't change.
* Agents can stay up across jobs, or take one job each and be deleted afterward. Orka doesn't watch your Azure DevOps queue, so the single-job model needs your own automation to deploy and delete VMs. See [Choose persistent or single-job agents](#choose-persistent-or-single-job-agents).

## Prerequisites

* An Orka 4.0 cluster with Apple silicon nodes, and the `orka` CLI. On Orka 3.x, use `orka3` in place of `orka`.
* An agent pool in Azure DevOps. See Microsoft's [Create and manage agent pools](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/pools-queues).
* A personal access token (PAT) with the **Agent Pools (read, manage)** scope. Azure DevOps uses the PAT only to register an agent, not afterward, so a short-lived PAT works. See Microsoft's [Register an agent using a personal access token](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/personal-access-token-agent-registration).
* An OCI registry for your agent image, with registry credentials in your Orka namespace. See [Manage registry credentials](https://docs.macstadium.com/orka/oci-images/oci-images-manage-registry-credentials.md).

## Set up Orka for Azure DevOps

**1. Build the agent image**

Deploy a VM from a base image, then connect to it over SSH. See [VM access ports](https://docs.macstadium.com/orka/orka-resources/vm-access-ports.md) for the SSH port.

```shell theme={null}
orka vm deploy azdo-image-build --image ghcr.io/macstadium/orka-images/tahoe:latest
```

To register agents with User Data later, the image needs the Orka 4.0 VM Tools. MacStadium's `latest` tags have them; not every version tag does yet.

In the VM, install Xcode and anything else your builds need. Then download the macOS ARM64 agent from the [agent releases page](https://github.com/microsoft/azure-pipelines-agent/releases) and extract it, following Microsoft's [Download and configure the agent](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent) steps. Use a path without spaces, such as `~/myagent`.

> [!WARNING]
>
> Don't run `config.sh` in the image, and don't push a VM whose agent is already registered. A configured agent has its own identity (the `.agent` and `.credentials` files), and every VM deployed from the image would share it. Each VM registers itself in a later step.

Push the VM to your registry, and check the push until it finishes:

```shell theme={null}
orka vm push azdo-image-build <REGISTRY>/<REPOSITORY>/azdo-agent:<TAG>
orka vm get-push-status <JOB_NAME>
```

After the push finishes, delete the build VM with `orka vm delete azdo-image-build`.

**2. Create a VM config**

Create a VM config from the agent image, so every agent VM deploys with the same settings:

```shell theme={null}
orka vm-config create azdo-agent --image <REGISTRY>/<REPOSITORY>/azdo-agent:<TAG>
```

See [VM configuration templates](https://docs.macstadium.com/orka/orka-cli-reference/vm-deployment-and-configuration.md#vm-configuration-templates) for CPU, memory, and placement options.

**3. Deploy and register agents**

Deploy a VM for each agent you want. `--generate-name` gives each VM a unique name:

```shell theme={null}
orka vm deploy azdo-agent --config azdo-agent --generate-name
```

On each VM, register the agent as the VM's user, then start it as a service. The commands use the VM's Orka name as the agent name, read from the [VM metadata](https://docs.macstadium.com/orka/orka-resources/working-with-vm-metadata.md), and prompt for the PAT so it doesn't end up in your shell history:

```shell theme={null}
cd ~/myagent
read -s AZP_TOKEN
AGENT_NAME=$(curl -s http://169.254.169.254/metadata/orka_vm_name)
./config.sh --unattended --url https://dev.azure.com/<ORGANIZATION> --auth pat --token "$AZP_TOKEN" --pool <POOL_NAME> --agent "$AGENT_NAME" --replace
./svc.sh install
./svc.sh start
```

For Azure DevOps Server, use your collection URL for `--url`. For every `config.sh` option, see Microsoft's [Unattended config](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent).

To register at first boot instead of over SSH, run `config.sh` from a [User Data](https://docs.macstadium.com/orka/orka-resources/userdata.md) script. User Data runs as root, and `config.sh` refuses to run as root by default, so run it as the VM's user, for example with `sudo -u`. Start the agent with `run.sh` in the background (for example, `sudo -u <USER> nohup ./run.sh &`) instead of `svc.sh`: the `svc.sh` service is a LaunchAgent, and at first boot there may be no logged-in session for it to start in. At first boot, the metadata service can take a moment to answer, so retry the `curl` for the VM's name until it returns one, for example `until AGENT_NAME=$(curl -sf http://169.254.169.254/metadata/orka_vm_name) && [ -n "$AGENT_NAME" ]; do sleep 2; done`.

Anyone with Kubernetes read access to the VM's pod or `VirtualMachineInstance` can read the script, so use a short-lived PAT or have the script fetch it from your secret store. Don't print the PAT or use `set -x`: the script's output goes to `/var/log/orka/userdata.log` in the VM. See [Keep credentials safe](https://docs.macstadium.com/orka/orka-resources/userdata.md#keep-credentials-safe).

**4. Target the pool from your pipeline**

Point your macOS jobs at the pool:

```yaml theme={null}
pool:
  name: <POOL_NAME>
```

The rest of your pipeline stays the same.

## Choose persistent or single-job agents

**Persistent agents** run as a service and take job after job until you delete the VM. Use them for pipelines that run often and benefit from warm caches. Clean up between jobs in your pipeline, for example with `workspace: clean: all`, because each job starts where the last one left off.

The `svc.sh` service is a launchd LaunchAgent, which starts only after the VM's user logs in. If an agent goes offline after its VM restarts, set up automatic login, as Microsoft describes in [Automatic login and lock](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent).

**Single-job agents** take one job and exit, so every job starts on a clean VM. Start the agent with `./run.sh --once` instead of `svc.sh`, then delete the VM after the job and deploy a new one. Orka doesn't do this for you: you need your own automation, using the `orka` CLI or the [Orka API](https://macstadium.github.io/orka-api-docs/), to keep enough fresh agent VMs registered. [Talk to MacStadium](mailto:support@macstadium.com) if you want help designing it.

## Plan capacity

Two limits decide how many jobs run at once:

* **Orka capacity:** each node runs at most two macOS VMs, so you can run up to two agents per node.
* **Azure DevOps parallel jobs:** Azure DevOps Services limits how many self-hosted jobs run at once by the parallel jobs on your organization. Extra agents beyond that sit idle. Azure DevOps Server doesn't charge for self-hosted parallel jobs. See Microsoft's [Configure and pay for parallel jobs](https://learn.microsoft.com/en-us/azure/devops/pipelines/licensing/concurrent-jobs).

## Network access

Agents only need outbound HTTPS from the VMs to Azure DevOps. Microsoft lists the domains to allow in the [self-hosted macOS agent FAQ](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent). If your cluster has VM internet isolation turned on, VMs can't reach Azure DevOps. See [VM internet isolation](https://docs.macstadium.com/orka/orka-resources/cluster-configurations.md#vm-internet-isolation).

To build images and deploy VMs, the machine where you run the `orka` CLI or your automation needs a network path to your Orka cluster: a [VPN client](https://docs.macstadium.com/orka/networking-with-orka-at-macstadium/vpn-connection.md) for testing, or a site-to-site tunnel for production.

## Troubleshooting

* **`config.sh` prints `Must not run with sudo`.** By default, the agent can't be configured as root. Run `config.sh` as the VM's user.
* **Two VMs show up as one agent, or an agent drops offline when another registers.** Agent names must be unique in a pool. Give each VM its own `--agent` name, for example the VM's name from the VM metadata, as in [Deploy and register agents](#set-up-orka-for-azure-devops).
* **An agent goes offline after its VM restarts.** The `svc.sh` service starts only after the VM's user logs in. See [Choose persistent or single-job agents](#choose-persistent-or-single-job-agents).
* **Jobs stay queued while agents are idle.** Check that the YAML names the right pool, that the agents meet the job's demands, and that you have enough parallel jobs.

For agent issues, see Microsoft's [Troubleshoot pipeline runs](https://learn.microsoft.com/en-us/azure/devops/pipelines/troubleshooting/troubleshooting).

## Related

* [Self-hosted macOS agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent) (Microsoft)
* [Bootstrap VMs with User Data](https://docs.macstadium.com/orka/orka-resources/userdata.md)
* [How Orka manages VMs](https://docs.macstadium.com/orka/orka-overview/how-orka-manages-vms.md)
* [Manage Orka service accounts for CI/CD](https://docs.macstadium.com/orka/orka-cluster-access/orka-cluster-manage-service-accounts.md)


## Related topics

- [AI agent integration with the Orka CLI](https://docs.macstadium.com/orka/orka-devops-integrations/claude-code.md)
- [Buildkite agents on Orka macOS VMs](https://docs.macstadium.com/orka/orka-devops-integrations/buildkite.md)
- [Jenkins plugin for Orka macOS build agents](https://docs.macstadium.com/orka/orka-devops-integrations/jenkins.md)
- [TeamCity plugin for Orka macOS build agents](https://docs.macstadium.com/orka/orka-devops-integrations/teamcity.md)
- [Harness CI on Orka: Orka-side setup](https://docs.macstadium.com/orka/orka-devops-integrations/harness.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.