|
| 1 | +# macOS Runners (Experimental) |
| 2 | + |
| 3 | +!!! warning |
| 4 | + This feature is in early stage and should be considered experimental. macOS runners on AWS have unique constraints compared to Linux and Windows runners. Please review all sections below before deploying. |
| 5 | + |
| 6 | +## Overview |
| 7 | + |
| 8 | +The module supports provisioning macOS-based GitHub Actions self-hosted runners on AWS using [Amazon EC2 Mac instances](https://aws.amazon.com/ec2/instance-types/mac/). macOS runners use the `osx` value for the `runner_os` variable and require **dedicated hosts** due to Apple's macOS licensing requirements. |
| 9 | + |
| 10 | +Key differences from Linux/Windows runners: |
| 11 | + |
| 12 | +- **Dedicated hosts required.** EC2 Mac instances must run on dedicated hosts. Each dedicated host can run **only one Mac VM at a time** (1:1 ratio). The module uses `RunInstances` directly instead of the `CreateFleet` API when `use_dedicated_host` is enabled. |
| 13 | +- **Longer boot times.** macOS instances can take 6–20 minutes to launch, significantly longer than Linux (~1 min) or Windows (~5 min). The default `minimum_running_time_in_minutes` for `osx` is set to 20 minutes to prevent premature scale-down. |
| 14 | +- **~50 minute host recycle time.** After an EC2 Mac instance is terminated, AWS performs install cleanup and software upgrades on the dedicated host before it becomes available again. This process takes approximately **50 minutes**, during which the host cannot launch a new instance. |
| 15 | +- **ARM64 (Apple Silicon) and x64 (Intel) support.** Both `mac1.metal` (Intel), `mac2.metal` (M1), and `mac2-m2.metal` (M2) instance types are supported. Set `runner_architecture` accordingly (`x64` or `arm64`). |
| 16 | +- **Only ephemeral mode is recommended.** Due to the long host allocation time and dedicated host cost model, we recommend using ephemeral runners. |
| 17 | + |
| 18 | +## Scaling caveats |
| 19 | + |
| 20 | +Running macOS runners at scale introduces challenges that do not exist with Linux or Windows runners: |
| 21 | + |
| 22 | +1. **1:1 host-to-VM ratio.** Unlike Linux where many instances share underlying hardware, each Mac VM requires its own dedicated host. To run N concurrent macOS jobs, you need at least N dedicated hosts. |
| 23 | +2. **Host recycle delay.** After a Mac instance is terminated, the dedicated host enters a ~50 minute cleanup cycle (scrubbing, software updates). During this window the host is unavailable. For bursty workloads, you need additional hosts to absorb demand while others recycle. |
| 24 | +3. **Capacity planning.** As a rule of thumb, if you expect N peak concurrent macOS jobs and each job takes T minutes, account for the extra ~50 minutes of host downtime per cycle when sizing your host pool. |
| 25 | + |
| 26 | +## Prerequisites |
| 27 | + |
| 28 | +Before deploying macOS runners, you must set up dedicated host infrastructure. There are two approaches: |
| 29 | + |
| 30 | +### Option A: Single dedicated host |
| 31 | + |
| 32 | +The simplest setup — allocate a single dedicated host and reference it directly. This works for low-scale or testing scenarios, but you must update the Terraform configuration whenever you replace the host. |
| 33 | + |
| 34 | +1. **Dedicated Host** — Allocate an EC2 dedicated host for your Mac instance type in the target availability zone. |
| 35 | + |
| 36 | +### Option B: Host resource group (recommended for scale) |
| 37 | + |
| 38 | +A host resource group allows you to associate **multiple dedicated hosts within an availability zone** into a logical group. When launching a Mac instance, AWS randomly selects an available host from the group. This means you can add, release, or replace individual dedicated hosts **without changing Terraform state or module inputs** — you only reference the group ARN, not individual host ARNs. |
| 39 | + |
| 40 | +This approach requires three resources: |
| 41 | + |
| 42 | +1. **Dedicated Hosts** — Allocate one or more EC2 dedicated hosts for Mac instance types in your target availability zones. |
| 43 | +2. **Host Resource Group** — Create an AWS Resource Groups group of type `AWS::EC2::HostManagement` and add your dedicated hosts as members. |
| 44 | +3. **License Configuration** — Create an AWS License Manager license configuration for Mac dedicated hosts (counting type: `Socket`). Associate it with the macOS AMI and the host resource group. The license configuration ARN is passed to the module via the `license_specifications` input. |
| 45 | + |
| 46 | +The [dedicated-mac-hosts example](examples/dedicated-mac-hosts.md) provides a ready-to-use Terraform configuration for all three resources. |
| 47 | + |
| 48 | +## Configuration |
| 49 | + |
| 50 | +### Basic setup |
| 51 | + |
| 52 | +```hcl |
| 53 | +module "runners" { |
| 54 | + source = "github-aws-runners/github-runners/aws" |
| 55 | +
|
| 56 | + # macOS-specific settings |
| 57 | + runner_os = "osx" |
| 58 | + runner_architecture = "arm64" # or "x64" for Intel Mac instances |
| 59 | + instance_types = ["mac2.metal"] |
| 60 | +
|
| 61 | + # Dedicated host settings (required for macOS) |
| 62 | + use_dedicated_host = true |
| 63 | + placement = { |
| 64 | + host_resource_group_arn = "<arn-of-your-host-resource-group>" |
| 65 | + } |
| 66 | + license_specifications = ["<arn-of-your-license-configuration>"] |
| 67 | +
|
| 68 | + # Recommended: ephemeral mode with a pool |
| 69 | + enable_ephemeral_runners = true |
| 70 | + delay_webhook_event = 0 |
| 71 | + enable_job_queued_check = true |
| 72 | +
|
| 73 | + # ...other common settings... |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +### AMI selection |
| 78 | + |
| 79 | +By default, the module selects an Amazon EC2 macOS Sequoia (macOS 15) AMI: |
| 80 | + |
| 81 | +- **ARM64:** `amzn-ec2-macos-15.*-arm64` |
| 82 | +- **x64:** `amzn-ec2-macos-15.*` |
| 83 | + |
| 84 | +You can override the AMI using filters or an SSM parameter: |
| 85 | + |
| 86 | +```hcl |
| 87 | +# Custom AMI filter |
| 88 | +ami = { |
| 89 | + filter = { |
| 90 | + name = ["amzn-ec2-macos-14.*-arm64"] |
| 91 | + state = ["available"] |
| 92 | + } |
| 93 | + owners = ["amazon"] |
| 94 | +} |
| 95 | +
|
| 96 | +# Or via SSM parameter |
| 97 | +ami = { |
| 98 | + id_ssm_parameter_arn = "arn:aws:ssm:region:account:parameter/path/to/mac/ami" |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### Multi-runner setup |
| 103 | + |
| 104 | +When using the multi-runner module, you can add a macOS runner configuration alongside Linux and Windows runners: |
| 105 | + |
| 106 | +```hcl |
| 107 | +multi_runner_config = { |
| 108 | + "mac-arm64" = { |
| 109 | + runner_config = { |
| 110 | + runner_os = "osx" |
| 111 | + runner_architecture = "arm64" |
| 112 | + instance_types = ["mac2.metal"] |
| 113 | + use_dedicated_host = true |
| 114 | + placement = { |
| 115 | + host_resource_group_arn = "<arn-of-your-host-resource-group>" |
| 116 | + } |
| 117 | + license_specifications = ["<arn-of-your-license-configuration>"] |
| 118 | + runner_extra_labels = ["osx", "arm64"] |
| 119 | + } |
| 120 | + matcherConfig = { |
| 121 | + labelMatchers = [["self-hosted", "osx", "arm64"]] |
| 122 | + exactMatch = false |
| 123 | + } |
| 124 | + } |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +## Instance launch behavior |
| 129 | + |
| 130 | +Because EC2 Fleet (`CreateFleet`) does not support launching instances onto dedicated hosts for `mac*.metal` instance types, the scale-up lambda automatically falls back to using `RunInstances` when `use_dedicated_host` is `true`. This is handled transparently — no additional configuration is needed. |
| 131 | + |
| 132 | +## User data and scripts |
| 133 | + |
| 134 | +The module uses macOS-specific templates for provisioning: |
| 135 | + |
| 136 | +| Script | Description | |
| 137 | +| --- | --- | |
| 138 | +| `user-data-osx.sh` | Boot script for macOS instances. Uses `ec2-user` and supports Homebrew. | |
| 139 | +| `install-runner-osx.sh` | Downloads and installs the GitHub Actions runner agent to `/opt/actions-runner`. | |
| 140 | +| `start-runner-osx.sh` | Registers the runner with GitHub and handles ephemeral cleanup. | |
| 141 | + |
| 142 | +Custom pre/post install scripts and job hooks (`hook_job_started`, `hook_job_completed`) work the same as on Linux. |
| 143 | + |
| 144 | +## Scale-down considerations |
| 145 | + |
| 146 | +macOS instances have a default minimum running time of **20 minutes** (vs. 5 for Linux, 15 for Windows) to account for the longer boot cycle. Adjust `minimum_running_time_in_minutes` if needed, but setting it too low risks terminating instances before they can execute a job. |
| 147 | + |
| 148 | +Additionally, remember that after an instance is terminated, the dedicated host enters a **~50 minute cleanup cycle** before it can launch a new instance. Aggressive scale-down can leave you with no available hosts during this window. |
| 149 | + |
| 150 | +```hcl |
| 151 | +# Override the minimum running time (not recommended to go below 20 for macOS) |
| 152 | +minimum_running_time_in_minutes = 25 |
| 153 | +``` |
| 154 | + |
| 155 | +## Cost considerations |
| 156 | + |
| 157 | +!!! note |
| 158 | + macOS dedicated hosts have a **minimum allocation period of 24 hours**. You are billed for the dedicated host for the full 24-hour period, regardless of instance usage. Plan your host allocation accordingly. |
| 159 | + |
| 160 | +- **Dedicated host costs**: Billed per-host, per-hour with a 24-hour minimum. Each host supports only one Mac VM at a time. See [EC2 Dedicated Hosts Pricing](https://aws.amazon.com/ec2/dedicated-hosts/pricing/). |
| 161 | +- **Instance costs**: Mac instances are billed on-demand only (no spot pricing available for Mac instances). |
| 162 | +- **Over-provisioning for recycle time**: Because hosts are unavailable for ~50 minutes after instance termination, you may need more dedicated hosts than your peak concurrency to avoid queuing. Factor this into your cost model. |
| 163 | +- **Pool sizing**: Keep pool sizes minimal to control costs, but large enough to avoid cold-start delays. |
| 164 | + |
| 165 | +## Known limitations |
| 166 | + |
| 167 | +- **No spot instance support.** EC2 Mac instances do not support the spot lifecycle. Runners always use on-demand pricing. |
| 168 | +- **1:1 host-to-VM ratio.** Each dedicated host can run only one Mac instance at a time. |
| 169 | +- **~50 minute host recycle time.** After instance termination, AWS performs cleanup and software upgrades on the dedicated host. The host is unavailable for approximately 50 minutes during this process. |
| 170 | +- **24-hour minimum host allocation.** Dedicated hosts cannot be released within 24 hours of allocation. |
| 171 | +- **Limited instance types.** Only `mac1.metal` (Intel x86), `mac2.metal` (M1 ARM64), and `mac2-m2.metal` (M2 ARM64) are available. Instance type availability varies by region. |
| 172 | +- **Longer startup.** Boot times of 6–20 minutes mean jobs will queue longer when no warm runners are available. |
| 173 | +- **No SSM Session Manager.** Unlike Linux instances, connecting via AWS Session Manager may not be available depending on your AMI. |
| 174 | +- **GHES not tested.** macOS runner support has not been validated against GitHub Enterprise Server. |
| 175 | + |
| 176 | +## Debugging |
| 177 | + |
| 178 | +- Check `/var/log/user-data.log` on the macOS instance for boot script output. |
| 179 | +- CloudWatch log streams under `<environment>/runners` will contain runner agent logs if CloudWatch logging is enabled. |
| 180 | +- Verify your dedicated host has available capacity in the EC2 console under **Dedicated Hosts**. |
| 181 | +- Ensure the host resource group ARN and license configuration ARN match what is configured in Terraform. |
| 182 | +- If runners fail to register, verify the GitHub App has the correct permissions and the SSM token path is accessible. |
| 183 | + |
| 184 | +## Example |
| 185 | + |
| 186 | +A complete example for setting up the dedicated host infrastructure is available at: |
| 187 | + |
| 188 | +- [Dedicated Mac Hosts example](examples/dedicated-mac-hosts.md) |
0 commit comments