Cloud Quotas for BYOC GitHub Actions Runners

On BYOC, cloud quotas cap concurrent GitHub Actions jobs. Size EC2, EBS, elastic IP, and NAT quotas in your stack region against your job concurrency.

Last verified:

Running GitHub Actions runners inside your own AWS account puts the concurrency ceiling in AWS Service Quotas: every concurrent job consumes vCPU against the EC2 running-instances quota, storage against the EBS volume quota, and one elastic IP, all in the single region where the stack was created. Size those quotas against your target concurrent job count before production workflows point at BYOC labels, then repeat the same exercise on GCP or Azure, where the identical shape appears under different quota names.

This guide covers the failure signature of a quota ceiling, the documented quota list per stack on each cloud, a worksheet that converts a concurrency target into the increases to request, and what a low ceiling costs in wall clock time and in dollars.

Diagnosis

A quota ceiling looks like a capacity problem and behaves like a hard wall. Jobs queue in GitHub, the runner never registers, and the workflow log shows nothing at all because the failure happened in the cloud API before any runner agent existed.

The signature that separates a quota from every other queue cause is the shape of the plateau. Running job count climbs, stops at the same number on every large fan-out, and stays there until finished jobs release capacity. The number is stable across days because a quota is a fixed integer, and it moves only when someone raises it.

Three properties of cloud quotas produce most of the surprise:

  • Quotas are regional. A stack in us-east-1 draws on us-east-1 quotas, and an increase applied in another region does nothing. The WarpBuild AWS BYOC quota reference states this directly: apply the increases in the region where the stack is created.
  • Quotas are per account or per project, and they are shared. A data pipeline that launches instances in the same account and region eats the same vCPU headroom your runners need.
  • Quotas count several resources at once. A fan-out can clear the vCPU quota and still fail on elastic IPs or on disk storage, because each concurrent job consumes one of each.

Match the symptom to the exhausted resource before requesting anything:

SymptomExhausted resource
Running job count plateaus at the same number on every fan-outEC2 vCPU, GCP CPUs, or Azure vCPU in the stack region
Plateau moves when you switch the runner to a smaller sizevCPU quota, since the quota counts vCPU rather than instances
Jobs start, then a later wave fails to launch after a spike in disk-heavy runsEBS volume storage or persistent disk quota
Launches fail while vCPU headroom is clearly freeElastic IPs, or free addresses in the subnet
Only spot-backed runners stall while on-demand runners launchThe separate spot vCPU quota, or spot capacity in that instance type
A single label never launches while other labels run fineRunner configuration or instance type availability rather than a quota

AWS names the failing resource in the API error, and the codes are worth recognizing: VcpuLimitExceeded for on-demand vCPU, MaxSpotInstanceCountExceeded for spot vCPU, AddressLimitExceeded for elastic IPs, and VolumeLimitExceeded for EBS. GCP returns QUOTA_EXCEEDED with the quota name in the message, and Azure returns a quota error naming the VM family.

Subnet address space is a ceiling that no quota page shows. WarpBuild recommends at least 250 usable IPs per subnet, and a subnet that runs out rejects launches the same way a quota does. Confirm the subnet mask before blaming Service Quotas.

Measure the plateau instead of estimating it. Queue Timings and Jobs reports are part of the WarpBuild CI observability surface, alongside snapshot runners, remote Docker builders, an MCP server, and the Action Debugger. A per-label queue wait that spikes only during large fan-outs, with duration steady, points at the ceiling. Queue wait that stays high on one label around the clock points at runner configuration instead.

Rule WarpBuild out early. Run as many jobs as your workflows need, since generally available Linux and Windows runners do not have plan-level concurrency caps and capacity adjusts dynamically. On BYOC that means the control plane will keep asking your cloud for instances up to whatever your workflows demand, and the first hard stop it meets is your account quota.

Fix

Raise the quotas to your target concurrency in the stack region, on the cloud that holds the stack. BYOC runs on AWS, GCP, and Azure, and the quota list per stack is documented for each.

AWS quotas per stack

Treat CON as the number of concurrently running jobs the stack must support.

ResourceQuota to requestNotes
EC2 instancesCON x vCPU per jobAdjust for instance type and for the split between spot and on-demand
EBS volumesCON x disk per jobAdjust provisioned IOPS if the runner profile needs it
Elastic IPs3 + CON3 for NAT gateways across 3 availability zones, 1 per concurrently running job
S3 buckets1One bucket serves the artifact cache, container layer cache, and telemetry
NAT gateways3One per availability zone
VPCs1One VPC per stack

Every row is applied in the stack region. The full table, with direct links into the AWS Service Quotas console for each limit, is in the AWS BYOC docs. Two caveats travel with it: quotas apply in the region where the stack is created, and the list is not exhaustive, so leave headroom rather than requesting the exact figure.

GCP quotas per stack

ResourceQuota to requestNotes
CPUCON x vCPU per jobAdjust for machine type and for preemptible against on-demand
Persistent disksCON x disk per jobWith Hyperdisk types, also raise provisioned IOPS and throughput quotas
In-use regional external IPv4 addresses3 + CONStatic addresses for Cloud NAT, plus 1 per concurrently running job
Cloud Storage1One bucket for artifact cache, container layer cache, and telemetry
Cloud NAT3One per stack
Networks1One VPC network
Subnetworks2One public and one private subnetwork per stack

The Hyperdisk row is the one teams miss. Disk capacity can be approved while provisioned IOPS and throughput stay at the default, and jobs then launch onto disks that are slower than the runner profile expects. The GCP BYOC docs link the Hyperdisk quota reference next to the disk row.

Azure quotas per stack

Azure mirrors the GCP shape: CPU at CON x vCPU per job, persistent disks at CON x disk per job, in-use regional external IPv4 addresses at 3 + CON, 1 object storage container, 3 NAT, 1 Vnet, and 2 subnetworks. BYOC on Azure is enabled for East US today, so plan capacity in that region and contact support for others.

Order of work

  1. Pick the concurrency target from real queue data rather than from the largest matrix in the repository. The largest matrix rarely runs alone, and other workflows share the account.
  2. Compute every row from that target with the worksheet below.
  3. Request increases in the stack region. Large vCPU and address increases can take days to approve, so file them before the migration date.
  4. Re-run a representative fan-out and watch where the plateau lands. If it lands below the target, one row was missed.
  5. Escalate anything the documented list does not explain. Reach out at [email protected].

Two levers reduce the quota you need. Multiple instance types per runner keep jobs launching when one type is short on capacity, which is the documented best practice for spot-backed runners. Smaller runner sizes lower vCPU per job, so a 4 vCPU profile needs half the vCPU quota of an 8 vCPU profile at the same concurrency. Windows runners are the exception worth planning around: the documented minimum for Windows Server 2022 on AWS is 8 vCPU with 6,000 IOPS and 500 MBps of disk throughput, so a Windows fleet consumes vCPU quota faster than a Linux fleet of the same job count. The BYOC runner fleet covers Linux across AWS, GCP, and Azure, with Windows on AWS and Azure.

Configuration

Start from the worksheet. It converts one concurrency target into the numbers you paste into the quota console. The example assumes 200 concurrent jobs, an 8 vCPU runner profile, and the recommended 150 GB root disk per job.

RowFormulaExample at 200 jobs, 8 vCPU, 150 GB
vCPU in the stack regionCON x vCPU per job200 x 8 = 1,600 vCPU
Disk storageCON x disk per job200 x 150 GB = 30,000 GB, rounded up to 30 TiB
Elastic or external IPv4 addresses3 + CON3 + 200 = 203
Object storage buckets11
NAT gateways33
Networks11
Subnetworks (GCP and Azure)22
Standby disk storagepool size x disk per job20 x 150 GB = 3,000 GB on top of the disk row

The standby row exists because a standby disk is a VM of the runner instance type that boots once and then shuts down, so a queued job starts in about 15 seconds. The shut-down VM stops consuming vCPU, and its disk stays allocated, so the pool adds to the disk quota and not to the vCPU quota. Spot instances are not supported for standby disks.

With quotas in place, workflows target BYOC labels the same way they target any runner label. Custom runner labels on BYOC follow the warp-custom- prefix and the name you gave the runner:

.github/workflows/test.yml
name: test
on:
  push:
    branches: [main]
  pull_request:

jobs:
  unit-tests:
    strategy:
      fail-fast: false
      max-parallel: 24
      matrix:
        shard: [1, 2, 3, 4, 5, 6, 7, 8]
    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 -- --shard=${{ matrix.shard }}/8

  integration-tests:
    needs: unit-tests
    runs-on: warp-custom-payments-use1-16x
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/integration.sh

  lint:
    runs-on: warp-ubuntu-latest-x64-2x
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint

Three configuration decisions are visible in that file:

  • max-parallel caps a single workflow's fan-out below the account ceiling, which keeps one noisy repository from consuming the whole quota while an increase is pending. Remove it once the quota lands.
  • The 8x and 16x custom runners draw on the same regional vCPU quota, so the worksheet uses a weighted vCPU per job when a fleet mixes sizes. At 150 jobs on 8 vCPU and 50 jobs on 16 vCPU, the request is 150 x 8 plus 50 x 16 = 2,000 vCPU.
  • The hosted warp-ubuntu-latest-x64-2x label in the same workflow consumes no quota in your account, which makes hosted labels a useful pressure valve for light jobs while a BYOC increase is in flight. The tradeoffs between the two placements are covered in the guide to hosted runners compared with BYOC runners.

Keep the elastic IP row in sync with any address policy you already run. Each concurrently running job takes an address, and teams that pin egress addresses for allowlisting have a second reason to track that number, described in the guide to static IPs for GitHub Actions runners.

Automate the parts that drift. Version the runner size and the standby pool size next to the quota worksheet, since a size bump silently doubles the vCPU quota a fleet needs.

Cost or Time Model

A quota increase costs nothing. Cloud providers bill the instances, disks, and addresses that actually run, so the ceiling is free while the traffic under it carries the usual charges. That makes the interesting question the cost of leaving the ceiling low.

Take a suite of 400 jobs at 6 minutes each, so 2,400 job-minutes per run. The wall clock depends entirely on the ceiling, and the billed minutes do not move.

Concurrency ceilingWavesWall clock per runJob-minutes per run
50848 min2,400
100424 min2,400
200212 min2,400

Raising the ceiling from 50 to 200 returns 36 minutes of wall clock per run. At 20 runs a day that is 720 minutes of engineer waiting removed daily, from a request that adds no charge on the cloud bill and no charge on the WarpBuild bill.

Now price the minutes. WarpBuild charges $0.002 per runner minute for BYOC Linux runners and $0.002 per runner minute for BYOC Windows runners, checked on 2026-08-13 against the WarpBuild pricing page. Your cloud provider bills the compute, storage, and data transfer directly at your own rates, including any spot pricing you have negotiated or configured.

Monthly BYOC runner minutesWarpBuild fee at $0.002 per minuteCloud compute
250,000$500Billed by your provider at your rates
1,000,000$2,000Billed by your provider at your rates
2,500,000$5,000Billed by your provider at your rates

The 2,400 job-minute suite above costs $4.80 per run in WarpBuild fees at every ceiling in the table, since the fee follows minutes rather than concurrency.

Two ongoing costs deserve a line in the model. Standby disks hold storage in your account while the VM is shut down, so a 20-slot pool at 150 GB carries 3,000 GB of block storage at your provider's rate whether or not jobs run. Reserved addresses are the other one: your provider charges for public IPv4 addresses on its own schedule, so request the address quota the fleet needs rather than the largest number you can imagine.

Re-run the worksheet whenever the runner size, the disk size, or the standby pool changes. A profile that moves from 8 vCPU to 16 vCPU doubles the vCPU quota needed at the same concurrency, and the first sign of that is a plateau that reappears at half the job count you sized for. The architecture behind those numbers is covered on the BYOC runners on AWS page, with the GCP equivalent on the BYOC runners on GCP page.

FAQ

Which AWS quotas do BYOC GitHub Actions runners need?

Per stack, WarpBuild documents EC2 instances at concurrent jobs times vCPU per job, EBS volumes at concurrent jobs times disk per job, elastic IPs at 3 plus concurrent jobs, 1 S3 bucket, 3 NAT gateways, and 1 VPC. Apply every increase in the region where the stack was created. The documented list is not exhaustive, so treat it as the floor.

Does WarpBuild cap how many BYOC runners I can run at once?

Run as many jobs as your workflows need. Generally available Linux and Windows runners do not have plan-level concurrency caps, and capacity adjusts dynamically. On BYOC the practical ceiling is the quota in your own cloud account and region, which is why the worksheet on this page starts from a target concurrent job count.

Do the same quotas apply on GCP and Azure?

The shape is the same and the names differ. GCP asks for CPUs, persistent disks, in-use regional external IPv4 addresses at 3 plus concurrency, 1 storage bucket, 3 Cloud NAT, 1 network, and 2 subnetworks, plus provisioned IOPS and throughput quotas when the disks are Hyperdisk. Azure mirrors GCP with a storage container and a Vnet, and BYOC on Azure is enabled for East US today.

Does raising a quota increase my cloud bill?

A quota sets a ceiling and reserves nothing, so your provider bills the instances and disks that actually run. WarpBuild charges $0.002 per runner minute for BYOC Linux runners and $0.002 per runner minute for BYOC Windows runners, checked on 2026-08-13 against the pricing page.

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.