Terraform for WarpBuild BYOC on AWS
The WarpBuild Terraform provider declares BYOC runner images and custom runner sets for GitHub Actions inside your own AWS account. Beta, AWS BYOC only.
Last verified:
You can declare the GitHub Actions runners that run inside your own AWS account as Terraform code with the WarpBuild provider, published at registry.terraform.io/providers/WarpBuilds/warpbuild/latest. Terraform support exists for BYOC on AWS: the provider is in beta, it supports AWS BYOC only, and it manages BYOC (AWS) runner images and custom runner sets, the same surface as the automation API.
This page covers the three objects a BYOC AWS setup is built from, which of them Terraform owns today, the two credentials involved, a working configuration with the labels it produces, and who pays for what once the runners are live.
Architecture
A BYOC AWS setup on WarpBuild stacks up in three layers, and they are created in that order.
Cloud connection. A CloudFormation stack in your AWS account provisions an IAM role that WarpBuild assumes to launch and terminate runner instances. The role follows the naming pattern warpbuild-<integration-id>.
WarpBuild stack. A stack is the group of infrastructure components in one region of your account that runners need: the VPC, the subnets, the security group, and an S3 bucket used for artifact cache, container layer cache, and runner telemetry. The stack name, the S3 bucket, and the region are fixed at creation time and cannot be changed afterward, so pick the region before you script anything.
Custom runner set. A runner set carries the instance types in priority order, the capacity type, the disk configuration, the runner image, and the pool size. Each set registers a GitHub Actions label formed as warp-custom- plus the runner name.
Terraform enters at the second and third layers. The provider reads an existing stack through a data source and writes the runner image and the runner set as managed resources.
| Object | Created where | Terraform coverage today |
|---|---|---|
| Cloud connection IAM role | AWS console, CloudFormation stack launched from the WarpBuild dashboard | Not managed |
| WarpBuild stack (region, VPC, subnets, S3 bucket) | WarpBuild dashboard | Read only, through the warpbuild_stack data source |
| BYOC runner image (AMI reference) | Automation API or Terraform | Managed, warpbuild_runner_image |
| Custom runner set (instance types, disk, pool) | Automation API or Terraform | Managed, warpbuild_runner |
| Runner instances at job time | WarpBuild control plane, in your AWS account | Not managed, and correctly so |
The last row is the important one. Terraform declares the shape of a runner set. It does not hold the individual EC2 instances in state, because those are launched just in time when a job is queued and terminated when the job ends. Putting per job instances in Terraform state would fight the scheduler.
At job time the flow is short. GitHub queues a job whose runs-on label matches a runner set. WarpBuild launches an instance from a launch template in your stack, registers a just-in-time runner against your GitHub organization, runs the job, then terminates the instance. Everything it creates follows a fixed naming pattern, which makes cost attribution and cleanup queries easy to write.
| Resource type | Naming pattern |
|---|---|
| S3 buckets | warpbuild-* |
| EC2 instances | warp-* |
| EBS volumes | warp-* |
| Launch templates | tmpl-warp-* |
Every provisioned resource also carries the managed-by: warpbuild tag, plus per job tags including warpbuild-runner-id, warpbuild-runner-labels, warpbuild-stack-id, and warpbuild-stack-name. Custom tags declared on the stack are added to everything the stack creates, which is the hook most teams use for chargeback.
Start with the provider block. Beta releases are prereleases, so a range like ~> 0.1 never matches one and the version has to be pinned exactly.
terraform {
required_providers {
warpbuild = {
source = "WarpBuilds/warpbuild"
# Prerelease version: pin it exactly
version = "0.2.0-beta"
}
}
}
# Reads WARPBUILD_API_KEY from the environment
provider "warpbuild" {}BYOC runs on AWS, GCP, and Azure. Terraform covers one of the three today.
| Cloud | BYOC setup guide | Terraform provider support |
|---|---|---|
| AWS | BYOC on AWS | Yes, in beta |
| GCP | BYOC on GCP | No, use the dashboard or the automation API |
| Azure | BYOC on Azure | No, use the dashboard or the automation API |
Permissions and Access
Two separate credentials do two separate jobs, and mixing them up is the most common setup mistake.
The WarpBuild API key authorizes Terraform to talk to the WarpBuild API. Create it at app.warpbuild.com/settings/api-keys with the ci scope and export it as WARPBUILD_API_KEY. The empty provider "warpbuild" {} block reads that variable, so the key stays out of the repository and out of the state file. The automation API guide grants the same key the cache scope as well when the key also drives cache operations.
The AWS IAM role authorizes WarpBuild to act inside your account. It is created once by the cloud connection CloudFormation stack, and Terraform never handles your AWS keys for runner operations. The role's permissions are grouped into policy contexts, each with a stated purpose.
| Policy context | What it is used for |
|---|---|
| FineGrainedEC2Permissions | Launch just-in-time runners, read instance types and offerings per region and availability zone, manage launch templates, tag and terminate instances |
| SpotServiceLinkedRolePermissions | Create and manage the EC2 Spot service linked role used for spot capacity |
| NetworkPermissions | Read regions, VPCs, subnets, route tables, and security groups for stack creation in import mode |
| StoragePermissions | Runner cache reads and writes plus runner system logs in the stack's S3 bucket |
| CloudFormationPermissions | Raise change set requests when a connection or a stack needs an upgrade |
If your security policy needs a narrower role, there are two supported paths documented in the AWS configuration guide: edit the CloudFormation template on the AWS redirect page before you apply it, or edit the role after it is created. The managed-by: warpbuild tag exists so you can write AWS IAM tag-based access control policies around WarpBuild-managed resources.
Runner instances get their own identity separately. An instance profile ARN is set per runner set, so a runner that deploys to production can hold a role that a runner running pull request tests never sees. Declare it on the runner resource and the scoping lives in code review with everything else.
Network posture for the stack is straightforward: the security group attached to runner instances blocks all inbound traffic, and outbound traffic is open so runners can reach GitHub, the WarpBuild control plane, and package registries. In create mode the CloudFormation template provisions that security group. In import mode you select an existing one when creating the stack.
For the compliance question that follows this one in most reviews: SOC 2 Type 2, with trust.warpbuild.com as the linked evidence.
Running Terraform itself from GitHub Actions keeps the key in one place. This workflow applies runner changes on merge and uses a WarpBuild hosted Linux runner for the Terraform job.
name: warpbuild-runners
on:
push:
branches: [main]
paths:
- "infra/warpbuild/**"
pull_request:
paths:
- "infra/warpbuild/**"
jobs:
terraform:
runs-on: warp-ubuntu-latest-x64-4x
permissions:
contents: read
defaults:
run:
working-directory: infra/warpbuild
env:
WARPBUILD_API_KEY: ${{ secrets.WARPBUILD_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
- run: terraform init
- run: terraform plan -out=tfplan
- if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplanConfiguration
A complete runner set is three blocks: a data source that looks up the stack, an image resource that points at an AMI, and a runner resource that describes the machines.
data "warpbuild_stack" "primary" {
alias = "ci-primary"
}
resource "warpbuild_runner_image" "linux_x64" {
alias = "ci-linux-x64"
stack_id = data.warpbuild_stack.primary.id
ami_id = "ami-0123456789abcdef0"
}
resource "warpbuild_runner" "ci_linux_large" {
name = "ci-linux-large"
provider_id = data.warpbuild_stack.primary.id
pool_size = 2
configuration = {
image = warpbuild_runner_image.linux_x64.id
byoc_sku = {
arch = "x64"
instance_types = ["m7a.xlarge", "m7i.xlarge"]
role_arn = "arn:aws:iam::123456789012:role/WarpBuildRunnerRole"
}
}
}Four things to know about that configuration.
instance_types is a priority list, not a single choice. The workflow asks for one label and WarpBuild picks an available instance type from the list. Keep the types close in price and performance so job behavior stays predictable, and give every set at least two types so jobs do not sit in the queue when one type is short on capacity in a region. Spot capacity makes a longer list more valuable, because spot availability moves.
role_arn is the instance profile discussed above. Leave it empty for runner sets that need no AWS access.
pool_size keeps warm capacity ready for the label. Raise it for the busiest labels and leave it low for long tail ones.
Disk configuration matters more than most teams expect. The minimum supported configuration is 100GB, 125MBps, and 3000 IOPS. The recommended configuration is 150GB, 400MBps, and 4000 IOPS. Windows Server 2022 x86-64 runners on AWS want more: at least an 8 vCPU instance type, with the m7a series recommended, 6000 IOPS, and 500 MBps of throughput. On AWS BYOC the Linux and Windows sets are the ones you declare here.
Settings that the provider schema does not expose yet stay in the WarpBuild dashboard: the IMDSv2 requirement toggle under Runner Specs, standby disks, static IP selection, and stack template upgrades. The resource schema tracks the automation API request bodies and the provider's API client is generated from the same specification, so check the resource documentation for the exact version you pinned before assuming a field exists.
Once terraform apply finishes, the label is live. The runner ID is the runner name with the warp-custom- prefix, and that string goes straight into runs-on.
name: build
on: [push]
jobs:
unit-tests:
runs-on: warp-custom-ci-linux-large
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm test
package:
needs: unit-tests
runs-on: warp-custom-ci-linux-large
steps:
- uses: actions/checkout@v4
- run: ./scripts/package.sh
docs:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- run: ./scripts/build-docs.shThe docs job shows the mixed pattern that most teams land on. Workloads that need your VPC, your data, or your instance profile run on the BYOC label. Everything else runs on a WarpBuild hosted label with no cloud account setup at all. Both kinds of label live in the same workflow file and both are one line to change.
Regions work differently across the two. WarpBuild hosted runners run in US and EU regions. A BYOC stack runs in the AWS region you choose when you create the stack, and since that choice is permanent, teams with two regions create two stacks and two sets of Terraform resources.
Who Manages What
Infrastructure as code moves a boundary rather than erasing one. This is where each object sits after the provider is in place.
| Object | Terraform | WarpBuild | You, in AWS |
|---|---|---|---|
| Cloud connection IAM role | Not managed | Defines the required permissions and the CloudFormation template | Applies the template, approves any permission edits |
| Stack (region, VPC, subnets, S3 bucket) | Read only | Creates and upgrades the stack template | Supplies network inputs in import mode, owns the S3 lifecycle policy |
| Runner image (AMI) | Declared as warpbuild_runner_image | Tracks image versions and purges old ones per settings | Builds and publishes the AMI when using a custom base image |
| Custom runner set | Declared as warpbuild_runner | Enforces the configuration when launching instances | Reviews the plan before it is applied |
| Runner instances per job | Not managed | Launches, registers with GitHub, terminates | Pays the EC2 and EBS charges |
| Cache and telemetry objects | Not managed | Writes cache entries and runner logs to the bucket | Sets the retention policy, 7 days is the recommendation |
| Service quotas | Not managed | Reports capacity failures | Raises EC2 vCPU, EBS volume, and Elastic IP quotas ahead of peak |
| GitHub Actions workflow labels | Produces the label string | Registers runners under that label | Edits runs-on in the workflow files |
Quotas are the one row that reliably bites teams during a first load test. Size EC2 vCPU quota as the peak number of concurrent jobs times the vCPUs per job, size EBS volume quota the same way against your disk configuration, and add headroom on Elastic IPs when static IPs are enabled, because one is attached per concurrently running job.
Billing splits along the same line. WarpBuild charges a per minute fee for BYOC runners and your cloud provider bills the compute directly.
| What you run | WarpBuild fee per minute | Compute billed by |
|---|---|---|
| BYOC Linux runners on AWS | $0.002 | Your AWS account |
| BYOC Windows runners on AWS | $0.002 | Your AWS account |
Hosted warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB) | $0.008 | Included |
Hosted warp-ubuntu-latest-arm64-4x (4 vCPU, 16 GB) | $0.006 | Included |
A worked model for a team running 200,000 Linux job minutes per month on a 4 vCPU shape, using GitHub public list prices from the GitHub Actions billing reference and the GitHub pricing page, both checked on 2026-08-13:
- GitHub-hosted 4-core Linux larger runner: 200,000 minutes at $0.012 per minute is $2,400 per month.
- WarpBuild hosted
warp-ubuntu-latest-x64-4x: 200,000 minutes at $0.008 per minute is $1,600 per month, a 33 percent lower list price against the GitHub 4-core larger runner. - WarpBuild BYOC on AWS: 200,000 minutes at $0.002 per minute is $400 per month in WarpBuild fees, plus the EC2 instance hours and EBS volumes on your own AWS bill at your account's rates, including any spot or committed use discounts you already hold.
Use the worked examples above with your measured job duration and AWS instance rates. WarpBuild's per-size rates are on the pricing page.
The BYOC line covers the WarpBuild per minute fee alone, so compare it against your own EC2 rate card before you move a fleet. Teams that already hold committed use discounts, or that run large instance types where the hourly EC2 rate dominates the per minute WarpBuild fee, are the ones that come out ahead on BYOC. Teams without that pricing usually start on hosted runners and move later, which is a label change in runs-on and a Terraform apply.
One enterprise note for deploy-heavy pipelines: egress costs are handled on the enterprise tier; see zero egress on the WarpBuild enterprise tier.
Every cost number on this page carries its source and the date it was checked, and the same numbers appear on the pricing page and in the evidence data behind it.
Next steps depend on where you are. If the AWS account is not connected yet, start with WarpBuild BYOC on AWS and the AWS configuration guide, then come back and put the runner sets in code. If you are on another cloud, see WarpBuild BYOC on GCP or WarpBuild BYOC on Azure, where runner sets are managed through the dashboard or the automation API. If the driver is where the machines sit rather than how they are declared, US data residency for GitHub Actions runners covers that question. The provider reference for every resource and data source lives on the Terraform provider docs page and in the provider repository.
FAQ
Does the WarpBuild Terraform provider work for GCP or Azure BYOC?
No. Terraform support exists for BYOC on AWS, and the provider is in beta with AWS BYOC only. BYOC runs on AWS, GCP, and Azure, so GCP and Azure runner sets are configured in the WarpBuild dashboard or through the automation API.
Which provider version should I pin?
Pin an exact prerelease version, such as 0.2.0-beta. Beta releases are prereleases, so a range like ~> 0.1 never matches them, and the resource schema may change between beta releases.
What credentials does the provider need?
One WarpBuild API key with the ci scope, created at app.warpbuild.com/settings/api-keys and exported as WARPBUILD_API_KEY. AWS access is handled by the IAM role that the WarpBuild cloud connection creates in your account.
Can Terraform create the cloud connection and the WarpBuild stack?
No. The cloud connection and the stack are created in the WarpBuild dashboard through a CloudFormation stack. Terraform reads an existing stack through the warpbuild_stack data source and manages runner images and custom runner sets against it.
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.