BYOC Runners on AWS for GitHub Actions
WarpBuild BYOC runs GitHub Actions runners as ephemeral EC2 instances inside your own AWS account. Architecture, IAM surface, configuration, and cost.
Last verified:
Self-hosted GitHub Actions runners on AWS mean EC2 instances that register against your repositories and execute workflow jobs inside your own VPC. WarpBuild BYOC gives you that placement without the controller you would otherwise operate: WarpBuild runs the control plane, your AWS account runs the instances, and a workflow moves onto them by changing one runs-on label.
This page covers the AWS architecture, the IAM surface WarpBuild asks for, the stack and runner configuration knobs, and the split of responsibility between your team and WarpBuild. BYOC runs on AWS, GCP, and Azure; the AWS path is the one with import mode and Terraform coverage.
Architecture
BYOC on AWS splits into a control plane that WarpBuild operates and a data plane that lives entirely in your AWS account.
The control plane holds the GitHub App connection, the runner configuration, the job queue watch, and the reconciliation loop that keeps runner capacity matched to queued jobs. It never holds your build data. When GitHub queues a job whose runs-on label matches one of your custom runners, the control plane calls the AWS API with the IAM role you granted and launches an EC2 instance in your VPC.
The data plane is a CloudFormation stack in one AWS region of your account. Creating it is documented in the AWS BYOC configuration guide, and the resource graph is drawn out in the BYOC AWS architecture reference. The stack owns:
- A VPC with public and private subnets across availability zones, and route tables carrying internet-bound traffic through the Internet Gateway.
- A security group attached to every runner instance the stack launches. In create mode CloudFormation provisions it. In import mode you select an existing one.
- An S3 bucket in the same region as the stack, used for the artifact cache, container layer cache, runner telemetry, and other workflow artifacts.
- EC2 instances, EBS volumes, and launch templates created per job and destroyed after it.
Three stack properties are immutable after creation: the stack name, the S3 bucket, and the region. Pick them deliberately. A naming convention that carries the product and the region reads well six months later, for example payments-use1 and payments-use2.
The S3 bucket has two documented key layouts, which matters when you write a lifecycle policy:
<bucket_name>/<org_id>/artifact_cache/<vcs_org>/<vcs_repo>/<vcs_ref>/<version>/<key>
<bucket_name>/runner/logs/all/<runner_id>.<log_file_name>Both prefixes grow without bound unless you expire them. The documented recommendation is a 7 day retention lifecycle rule, adjusted to your own retention policy.
Instance lifecycle
Each GitHub Actions job runs on a distinct, freshly provisioned EC2 instance that is terminated when the job completes. There is no shared state between jobs and no runner that survives to serve a second job. That property is what makes autoscaling correct rather than merely convenient: the queue depth sets the instance count, and a finished job leaves nothing behind for the next one to inherit.
Cold boot time on BYOC is handled by standby disks. A standby disk is a VM of the same instance type as the runner, booted once so that networking and machine setup complete, then shut down. When GitHub requests a runner, WarpBuild boots a standby disk if the pool has one, which puts job start at roughly 15 seconds. If the pool is empty, a fresh VM is created instead. The pool size is configured per custom runner. WarpBuild does not charge for standby disks; the shut-down VM stops billing compute while its EBS volume keeps billing storage in your account. Spot instances are not supported for standby disks, because a Spot instance can be reclaimed at any time and the pool would stop being a guarantee.
Resource naming and tagging
Everything the stack creates is discoverable by name and tag, which is what makes cost attribution work in AWS Cost Explorer.
| Resource type | Naming pattern |
|---|---|
| S3 buckets | warpbuild-* |
| EC2 instances | warp-* |
| EBS volumes | warp-* |
| Launch templates | tmpl-warp-* |
WarpBuild adds a managed-by: warpbuild tag to provisioned resources, plus a documented tag set on each runner instance: warpbuild-github-org, warpbuild-runner-labels, warpbuild-runner-id, warpbuild-stack-id, and warpbuild-stack-name. Custom tags you define on the stack are applied to everything it creates. Filtering a cost report by warpbuild-runner-labels gives you spend per runner class without any extra instrumentation.
Permissions and Access
Connecting an AWS account creates one IAM role, named warpbuild-<integration-id>, through a CloudFormation template you apply yourself. The role is the entire access surface. Its permissions are grouped into named policy contexts so that you can read, edit, or reject them one block at a time.
| Policy context | What it is used for |
|---|---|
FineGrainedEC2Permissions | Launch just-in-time runners, describe instance types and offerings per region and availability zone, manage launch templates, create and deregister images, tag instances |
SpotServiceLinkedRolePermissions | Create and manage the EC2 Spot service-linked role, and pass roles to runner instances |
NetworkPermissions | Describe regions, VPCs, subnets, route tables, security groups, and internet gateways; create and delete the endpoints the stack needs in import mode |
StoragePermissions | EBS snapshot actions, plus the S3 actions behind the WarpBuild cache action and runner system logs |
CloudFormationPermissions | Submit changeset requests when a connection or a stack needs an upgrade |
The EC2 block is the one worth reading closely, because it is where launch authority lives:
Policies:
- PolicyName: "FineGrainedEC2Permissions"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "ec2:DescribeInstanceTypes"
- "ec2:DescribeInstanceTypeOfferings"
- "ec2:DescribeInstances"
- "ec2:RunInstances"
- "ec2:CreateFleet"
- "ec2:RequestSpotInstances"
- "ec2:CancelSpotInstanceRequests"
- "ec2:DescribeSpotInstanceRequests"
- "ec2:DescribeSpotPriceHistory"
- "ec2:CreateLaunchTemplate"
- "ec2:DeleteLaunchTemplate"
- "ec2:ModifyLaunchTemplate"
- "ec2:TerminateInstances"
- "ec2:CreateImage"
- "ec2:DeregisterImage"
- "ec2:DescribeImages"
- "ec2:CreateTags"
- "ec2:DeleteTags"
Resource: "*"
- PolicyName: "SpotServiceLinkedRolePermissions"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "iam:CreateServiceLinkedRole"
- "iam:PassRole"
Resource: "arn:aws:iam::*:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot"Two customization paths are supported. You can edit the CloudFormation template on the AWS redirect page before you apply the connection stack, or you can edit the role after it exists. Teams that want a hard boundary use IAM tag-based access control keyed on managed-by: warpbuild, which scopes the role to resources WarpBuild created. The full policy document is expanded in the configuration docs.
What the runner itself can reach
The connection role governs what WarpBuild can do to your account. A separate control governs what your build steps can do: the instance profile.
WarpBuild injects no permissions into runner workloads. The permissions a job has are exactly the ones carried by the instance profile you attach, configured per runner under Custom Runner configuration and the Instance Profile ARN field. Because the setting is per runner, a deploy runner and a unit-test runner can carry different AWS authority under the same stack.
Attaching a profile takes one grant on the WarpBuild connection role:
aws iam put-role-policy \
--role-name warpbuild-<integration-id> \
--policy-name PassRolePolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "<role linked to the runner instance profile>",
"Condition": {
"StringEquals": {
"iam:PassedToService": "ec2.amazonaws.com"
}
}
}
]
}'Verify it with aws iam simulate-principal-policy before you wire a workflow to it. The full walkthrough is in the instance profile guide.
Runner instances support both IMDSv1 and IMDSv2 by default. Setting Require IMDSv2 on the runner, either at creation time or on the Update Runner page, forces token-authenticated metadata requests and closes the classic path where a request-forgery bug in a build step reads instance credentials. If your build steps read instance metadata directly, confirm the client library sends the IMDSv2 token before you flip it.
Network posture rounds it out. The runner agent polls outbound, so runner instances accept inbound connections from nowhere, including from inside the VPC. The security hardening guide recommends a security group with an empty inbound rule set and outbound access to api.warpbuild.com, github.com, api.github.com, and your package registries. Network ACLs handle the second question, which is whether one runner can reach another; runner instances never need to talk to each other, so denying intra-subnet traffic is safe.
Configuration
Bring the AWS side up first, then create the stack, then create runners.
Prerequisites. You need permission to run a CloudFormation stack, a VPC with at least one public and one private subnet with internet connectivity, and an S3 bucket in the stack region. The documented recommendation is three public and three private subnets in different availability zones, which widens instance type availability, and at least 250 usable IP addresses per subnet so that concurrency is not capped by address exhaustion.
Quotas. The runner resources are created in your account and consume your quotas. For a target of $CON concurrent jobs, per stack:
| Resource | Quota to request |
|---|---|
| EC2 instances | $CON x vCPU per job |
| EBS volumes | $CON x disk TB per job |
| Elastic IPs | 3 plus $CON, when static IPs are enabled |
| S3 buckets | 1, shared by artifact cache, layer cache, and telemetry |
| VPCs | 1 |
Quota increases apply per region, so raise them in the region where the stack lives. The BYOC AWS docs carry the full list and the console links.
Stack modes. Easy create builds a new VPC and its resources to the documented best practices. Import mode takes an existing VPC, subnets, and security group as inputs and is supported on AWS only.
Custom runners. A custom runner is a named instance configuration: one or more instance types in priority order, disk size and performance, IP configuration, standby disk count, and an optional instance profile. Listing several instance types matters more than it looks. The workflow references a single label, and WarpBuild picks an available instance type from your ordered list, so a capacity gap in one type does not park jobs in the queue. Choose types that are close in price and performance, for example m7a.xlarge and m7i.xlarge, so job duration stays predictable.
Disk configuration is the most common cause of slow BYOC jobs. The documented minimum is 100 GB, 125 MBps, and 3000 IOPS. The documented recommendation is 150 GB, 400 MBps, and 4000 IOPS. Windows Server 2022 x86-64 runners on AWS want at least 8 vCPU, the m7a series, 6000 IOPS, and 500 MBps.
Spot instances suit short jobs that tolerate interruption, and AWS bills them at its published Spot rates in your account, where AWS states Spot Instances run at up to a 90 percent discount compared to On-Demand prices (AWS EC2 Spot Instances, checked on 2026-08-13). Pair Spot with a longer fallback instance type list, since availability is what strands a queued job.
Two behaviors need no configuration. If the instance types you select expose local NVMe instance store volumes, such as the i3, c5d, m5d, and r5d families, the runner detects them at boot and mounts them at the work directory; multiple devices are combined into a RAID-0 array first. This is Linux images only. Nested virtualization is enabled automatically when every instance type in the runner set supports it.
Referencing runners in a workflow
A BYOC runner is addressed by its Runner ID, which is the runner name with the warp-custom- prefix. Nothing else in the workflow changes.
name: ci
on:
push:
branches: [main]
pull_request:
jobs:
unit-tests:
runs-on: warp-custom-payments-use1-8x
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm test
integration-tests:
needs: unit-tests
runs-on: warp-custom-payments-use1-16x
steps:
- uses: actions/checkout@v4
- run: ./scripts/integration.shMixed fleets are common while a migration is in flight. WarpBuild-hosted labels and BYOC labels coexist in one workflow, so a matrix can send the heavy leg into your account and leave the rest on hosted capacity:
name: build
on: [push]
jobs:
lint:
runs-on: warp-ubuntu-latest-x64-2x
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
package:
strategy:
matrix:
include:
- runner: warp-ubuntu-latest-x64-8x
target: linux-amd64
- runner: warp-ubuntu-latest-arm64-8x
target: linux-arm64
- runner: warp-windows-latest-x64-8x
target: windows-amd64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- run: ./scripts/package.sh ${{ matrix.target }}Those hosted labels and their rates come from the runner catalog:
| Label | OS | vCPU | RAM | Storage | Hosted rate per minute |
|---|---|---|---|---|---|
warp-ubuntu-latest-x64-2x | Ubuntu 24.04 | 2 | 8 GB | 150GB SSD | $0.004 |
warp-ubuntu-latest-x64-4x | Ubuntu 24.04 | 4 | 16 GB | 150GB SSD | $0.008 |
warp-ubuntu-latest-x64-8x | Ubuntu 24.04 | 8 | 32 GB | 150GB SSD | $0.016 |
warp-ubuntu-latest-x64-16x | Ubuntu 24.04 | 16 | 64 GB | 150GB SSD | $0.032 |
warp-ubuntu-latest-x64-32x | Ubuntu 24.04 | 32 | 128 GB | 150GB SSD | $0.064 |
warp-ubuntu-latest-arm64-8x | Ubuntu 24.04 | 8 | 32 GB | 150GB SSD | $0.012 |
warp-windows-latest-x64-8x | Windows Server 2022 | 8 | 32 GB | 256GB SSD | $0.032 |
warp-windows-latest-x64-16x | Windows Server 2022 | 16 | 64 GB | 256GB SSD | $0.064 |
On AWS BYOC the supported guest operating systems are Linux and Windows Server 2022 x86-64; the AWS hypervisor does not expose the Hyper-V features their Azure equivalents do, and macOS runners stay on WarpBuild-hosted capacity.
Custom VM images
BYOC runners can boot your own AMI instead of a WarpBuild image, which is how teams that must ship a hardened base or a preloaded toolchain keep BYOC compliant with their image policy. Linux and Windows AMIs are supported. A Linux image has to use systemd, because the WarpBuild agent runs as a unit, and it has to carry curl, wget, bash, jq, and libicu, the last of which the GitHub Actions runner needs for its .NET runtime. Ubuntu and Amazon Linux 2023 both qualify. Keep tar, gzip, coreutils, and shadow-utils in place. The custom VM images guide includes a working Packer template.
Owning the image also means owning its patch cycle. WarpBuild-managed images are updated by WarpBuild; a custom AMI is rebuilt on your schedule.
The Terraform path
Terraform support exists for BYOC on AWS. The WarpBuild Terraform provider is published on the Terraform Registry and manages BYOC runner images and custom runner sets, the same surface as the automation API. It authenticates with a WarpBuild API key carrying the ci scope, supplied as WARPBUILD_API_KEY. The provider is in beta, so pin the version exactly:
terraform {
required_providers {
warpbuild = {
source = "WarpBuilds/warpbuild"
version = "0.2.0-beta"
}
}
}Terraform coverage is scoped to AWS BYOC. Resource reference, data sources, and a full example live on the AWS BYOC Terraform page.
Who Manages What
The reason to read a BYOC page carefully is to find out which team gets paged at 2am. Here is the split.
| Responsibility | Your AWS account | WarpBuild |
|---|---|---|
| Provisioning the VPC, subnets, security group, and S3 bucket | Yes, through the CloudFormation stack you apply | Authors the template and the create and import flows |
| Launching and terminating runner instances | Instances run here and consume your quotas | Watches the job queue and drives the AWS API |
| Runner image patching | Only for a custom AMI you supply | Patches WarpBuild-managed runner images |
| Cloud bill for EC2, EBS, and data transfer | Yes, billed by AWS at your rates | Not billed |
| WarpBuild fee | $0.002 per runner minute | Metered and invoiced |
| Job data, caches, logs, and artifacts | Held in your S3 bucket and on your instances | Control plane metadata only |
| IAM authority granted to build steps | Instance profile you attach per runner | Injects no permissions into workloads |
| Quota increases and capacity planning | Yes, per region | Documents the quota model |
| Runner lifecycle bugs and control plane incidents | Escalate | Owns |
Two consequences follow. Build data and cache contents stay inside your account and your region, which is the usual reason a regulated team picks BYOC over hosted capacity. And capacity limits become AWS quota questions rather than vendor questions, which is why the quota table above is worth filing before the first migration rather than after it. If your requirement is regional placement rather than account placement, WarpBuild-hosted runners cover it too: US and EU are the regions WarpBuild names publicly, and the US data residency page covers that path.
A worked cost model
Three ways to run the same 8 vCPU Linux job, priced against GitHub's published list prices. GitHub rates come from the GitHub Actions billing reference and the plan page at github.com/pricing, checked on 2026-08-13. WarpBuild rates come from the WarpBuild pricing page.
| Shape | GitHub-hosted list price per minute | WarpBuild-hosted rate per minute | WarpBuild BYOC fee per minute |
|---|---|---|---|
| Linux 2 vCPU, 8 GB | $0.006 (ubuntu-latest, private repositories) | $0.004 | $0.002 |
| Linux 4 vCPU, 16 GB | $0.012 (4-core larger runner) | $0.008 | $0.002 |
| Linux 8 vCPU, 32 GB | $0.022 (8-core larger runner) | $0.016 | $0.002 |
| Linux 16 vCPU, 64 GB | $0.042 (16-core larger runner) | $0.032 | $0.002 |
| Linux 32 vCPU, 128 GB | $0.082 (32-core larger runner) | $0.064 | $0.002 |
| Windows 8 vCPU, 32 GB | $0.042 (8-core larger runner) | $0.032 | $0.002 |
| Windows 16 vCPU, 64 GB | $0.082 (16-core larger runner) | $0.064 | $0.002 |
Take a team burning 200,000 Linux minutes a month on an 8 vCPU shape.
- GitHub-hosted 8-core larger runner: 200,000 x $0.022 = $4,400 per month.
- WarpBuild-hosted
warp-ubuntu-latest-x64-8x: 200,000 x $0.016 = $3,200 per month. Arithmetic on the two list prices: (0.022 - 0.016) / 0.022 = 27 percent lower list price, GitHub list price checked on 2026-08-13. - WarpBuild BYOC on AWS: 200,000 x $0.002 = $400 per month in WarpBuild fees, plus whatever AWS bills your account for the EC2 instances, EBS volumes, and data transfer those jobs consume, at your own On-Demand, Spot, or Savings Plan rates.
Add 50,000 Windows minutes on an 8 vCPU shape and the same three lines read $2,100, $1,600, and $100 plus your AWS bill. The BYOC line covers the WarpBuild fee alone, so the honest comparison is $400 plus your EC2 bill against $4,400, and whether it lands depends on your instance mix, your Spot ratio, and your committed-use discounts. Model it with a real month of workflow minutes before you migrate, and price the storage the cache and telemetry prefixes will hold.
Use the table above with your measured job duration and AWS instance rates. WarpBuild's per-size arithmetic is also on the pricing page.
BYOC add-ons are included in the per-minute fee. Egress costs during deploy-heavy jobs are handled on the enterprise tier; see zero egress on the WarpBuild enterprise tier.
Operating it after the migration
CI observability streams OpenTelemetry system metrics from the runner agent and correlates them with GitHub Actions job logs, which is how you tell an undersized disk from a slow test suite. The Action Debugger pauses a workflow and opens an SSH session on the runner, which is the fastest way to reproduce a failure that only happens on the fleet. Snapshot runners are the one product surface BYOC does not carry; they do not support BYOC, Windows, or macOS runners, and standby disks fill the cold-start role instead.
SSO is available for a flat $250 per month, whatever the user count, listed with the rest of the rates on the pricing page.
The other two clouds are covered on the GCP BYOC page and the Azure BYOC page.
FAQ
Does WarpBuild need inbound network access to my AWS account?
No. The GitHub Actions runner agent holds an outbound long poll, so the recommended security group blocks all inbound traffic and allows outbound traffic to api.warpbuild.com, github.com, api.github.com, and your package registries. WarpBuild reaches your account through the IAM role created by the CloudFormation connection stack, and the instances expose no inbound path.
What does WarpBuild charge for BYOC runners on AWS?
$0.002 per runner minute for Linux runners and $0.002 per runner minute for Windows runners. AWS bills your own account for EC2, EBS, and data transfer at your rates.
Can I manage AWS BYOC runners with Terraform?
Yes. Terraform support exists for BYOC on AWS. The WarpBuild provider is published on the Terraform Registry, covers BYOC runner images and custom runner sets, and authenticates with a WarpBuild API key carrying the ci scope. Terraform coverage is scoped to AWS BYOC.
Are snapshot runners available on BYOC?
No. Snapshot runners do not support BYOC runners, Windows runners, or macOS runners. On BYOC the equivalent start-time lever is a standby disk pool, which pre-initializes VMs of the runner instance type so a queued job boots in about 15 seconds.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.