Flex vs Fleet
Choose the right RunsOn mode for your GitHub Actions runners on AWS.
RunsOn runs ephemeral GitHub Actions runners in your AWS account in two modes, Flex and Fleet. Both launch a fresh EC2 runner for each job, and one license covers both.
Start with Flex. Add Fleet, or move to it, when a platform team governs standardized runner specs centrally, or when you need self-hosted runners at the enterprise level, covering many GitHub organizations at once.
Which one to use#
| Your situation | Use |
|---|---|
| You are getting started, or your jobs need different instance types, images or disks, at up to about 50,000 jobs a day | Flex |
| Your runner specs are standardized, and a platform team governs them centrally | Fleet |
| You need self-hosted runners at the enterprise level, covering many GitHub organizations at once | Fleet |
| Most of your jobs fit standard runners, and some need an unusual one | Both |
Three other reasons to pick Fleet at any volume:
- a platform team should own the runner specs, and change them through Terraform review
- nothing should call into your AWS account: Fleet pulls jobs from GitHub, with no inbound webhook
- large matrices should honor GitHub’s
strategy.max-parallelbefore capacity launches
Where Fleet differs from Flex#
Fleet runs the same runners as Flex, with the same capabilities, except:
- Per-job runner configuration. Workflows pick a named fleet. They cannot override CPU, RAM, image, disk, networking or extras.
- Shared EFS volumes. Not available in Fleet; shared EFS volumes are a Flex feature.
- Cost automation. Fleet has per-job cost estimates, cost-allocation tags and the daily budget alarm (starting with v3.5.0), but no email cost report. Fleet does not activate the cost-allocation tag for you: activate it in AWS Billing so the budget sees the stack’s spend.
- Installation. Terraform / OpenTofu only, and one stack serves one GitHub organization or one enterprise. Flex also installs with CloudFormation.
What changes in your workflow#
Flex
In Flex, the label is the runner spec. Each job writes the CPU, family, image, disk or extras it needs, and the next job can ask for something else. Changing a runner is a workflow commit:
jobs: lint: runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64 test: runs-on: runs-on=${{ github.run_id }}/runner=16cpu-linux-x64/extras=s3-cache build-arm64: runs-on: runs-on=${{ github.run_id }}/cpu=8/family=c8g/image=ubuntu24-full-arm64/volume=200gbFleet
In Fleet, the label is a name. Jobs pick one of the runner fleets the platform team published, and the spec behind each name lives in Terraform. Changing a runner is a Terraform change, and workflows can’t override it per job:
jobs: lint: runs-on: runs-on/fleet=linux-small/env=production test: runs-on: runs-on/fleet=linux-large/env=production build-arm64: runs-on: runs-on/fleet=linux-arm64/env=productionSee job labels for every Flex label, and the Fleet installation guide for how fleets are defined.
How runners are configured in each mode#
The same runner capabilities work in both modes. In Flex, the workflow author sets them per job, in the job’s label. In Fleet, the platform team sets them per runner fleet, in Terraform.
| Setting | Flex | Fleet |
|---|---|---|
| Runner shape and image | The workflow selects them per job, directly or through .github/runs-on.yml. | The platform team defines them in the Terraform runner catalog. |
| Magic Cache and tmpfs | The workflow opts in with extras labels. | The platform team adds extras to the runner definition. |
| Nested virtualization | The workflow adds the nested-virt label. | The platform team enables nested-virt on the runner definition. |
| Private networking | The stack enables private subnets, and a job opts in through a label. | The stack enables private subnets, and the platform team sets private on the runner definition. |
| Workflow target | A label describes the runner, for example runs-on=${{ github.run_id }}/runner=2cpu-linux-x64. | A stable label names an approved runner fleet, for example runs-on/fleet=linux-small/env=production. |
The runner capabilities matrix has the coverage per platform, and Flex concepts imported into Fleet has the Terraform mapping.
RunsOn Flex#
Workflow authors describe the runner each job needs in its label. RunsOn provisions a dedicated EC2 runner for that job, which runs it and terminates.
Flex architecture#
Flex runs in your AWS account and supports CloudFormation or Terraform /
OpenTofu installs. GitHub sends workflow_job webhooks through API Gateway and
Lambda. The ingress validates each event and places the work on SQS for the ECS
Fargate control plane.
The control plane provisions a dedicated EC2 runner for each job. The runner registers with GitHub, runs the job, and terminates. DynamoDB stores control-plane state, Secrets Manager stores GitHub credentials, and S3 provides cache and bootstrap storage. CloudWatch and SNS provide logs, dashboards, alerts, and reports.
When private runners are enabled, the built-in CloudFormation network creates private subnets, a NAT Gateway with a static egress IP, and an S3 gateway endpoint. Terraform / OpenTofu installs use the VPC, subnets, and egress path supplied by the platform team.
Start with the Flex installation guide.
RunsOn Fleet#
A platform team defines runner fleets in Terraform: shape, image, networking, capacity and GitHub runner group. Workflows target a fleet by name, and GitHub assigns each job to a runner scale set before RunsOn launches capacity for it.
Normal scale-set operation only needs self-hosted-runner management permissions.
Automatic reruns after Spot interruptions also require GitHub Actions read/write.
Set retry: false on a runner definition when you do not grant that permission.
Fleet architecture#
Fleet runs in your AWS account and is deployed with Terraform / OpenTofu. Its
fleetd control plane runs on ECS Fargate and uses a pull-based connection to
GitHub. There is no inbound webhook, API Gateway, or SQS queue in the demand
path.
Each runner fleet maps to one GitHub runner scale set attached to an existing
runner group. One Fleet runtime serves either one GitHub organization or one
enterprise boundary. fleetd maintains a message session with each scale set
and reacts only to jobs that GitHub assigns.
Fleet records durable capacity claims in DynamoDB. It stores cache data and runner bootstrap configuration in S3, and reads its runtime configuration and GitHub credentials from Secrets Manager. For each assigned job, Fleet uses ready hot or stopped standby capacity first, then launches a new Linux or Windows EC2 runner. The runner registers with its scale set, runs one job, and terminates.
Start with the Fleet installation guide.
Using both#
Flex and Fleet can run side by side, in the same AWS account and VPC. Their control planes and configuration stay separate.
- Fleet publishes named runner fleets for the common workloads: Linux, Windows, GPU, build or deployment jobs.
- Flex takes the exceptions: an unusual instance type, image or disk size that nobody has published as a fleet.
Repositories can target Fleet by default and use a Flex label only on the jobs that need per-job control.
Licensing#
The same commercial license covers Flex and Fleet for the purchasing legal entity, across the GitHub organizations and AWS accounts it owns or controls. Affiliates are covered only when the applicable plan or order includes them.
Usage is the combined number of runner launches across both products each month. One launch normally corresponds to one GitHub Actions job. RunsOn does not automatically block jobs when usage exceeds a tier. After two consecutive calendar months above the threshold, RunsOn gives at least 15 days’ notice to reduce usage or move to the matching tier. A mid-term upgrade is prorated for the rest of the subscription term.
See Licensing and usage and current pricing.
Watch the walkthroughs#
Each mode in about eight minutes: Flex first, then Fleet.