GitHub Actions runner sizes: which size for which job, from 1 to 32 vCPU
Choosing a runner size feels like a speed decision and is really a money decision with a speed side effect. A larger size costs more for every minute, in proportion to the machine, so it only saves money when it takes the minutes down by more than the rate goes up. Sometimes it does. Often it buys a shorter wait for a longer bill, which can be exactly what you want, as long as you know that is the trade. This guide gives the sizes we sell, the method for picking one, and the three cases where the answer is not “bigger”.
What is on offer
The table is the Ubuntu x64 set, which is the one most workflows use. The label is the size, the operating system and its version joined together, for example runnersio-4vcpu-ubuntu-2404, and the same size costs the same on every Ubuntu version. The labels page has every label, including ARM and Windows, and the pricing page has what each costs a minute.
| Size | Memory | Disk | Replaces on GitHub |
|---|---|---|---|
| 1 vCPU | 4 GB | 50 GB | ubuntu-slim |
| 2 vCPU | 7 GB | 50 GB | ubuntu-latest in a private repository |
| 4 vCPU | 15 GB | 50 GB | ubuntu-latest in a public repository |
| 8 vCPU | 30 GB | 50 GB | no standard equivalent |
| 16 vCPU | 60 GB | 50 GB | no standard equivalent |
| 32 vCPU | 125 GB | 400 GB | no standard equivalent |
The two rows in the middle are where a migrated workflow starts. GitHub gives a standard Linux runner 2 vCPU and 8 GB in a private repository and 4 vCPU and 16 GB in a public one, and the migration keeps the size GitHub was giving you, so a private repository lands on the 2 vCPU label and a public one on 4. That is a sensible baseline to measure from, not a recommendation to stay.
The method: read the machine, then change one thing
Run the workflow you care about at the baseline size and open the job’s page. The Resources tab plots memory, disk, CPU and network over the run, and Details gives Ran for and what the run cost. Look at the slowest step, not the average over the job, and read three things.
CPU. If it sits near the ceiling for the slow step, the step can use more cores and a larger size should shorten it. If it sits near a single core, it is serial, and no size will help. A linker, a single-threaded test runner and a download all look like this. Memory. If it climbs to the limit, the job is at risk of slowing down or being killed, and a larger size is a fix for correctness before it is a fix for speed. Disk. If it fills, the job will fail, and the fix is either a clean-up step or a size with more room.
Then change one thing, usually one size up, run it a few times and compare Ran for. Do not change the size and the cache in the same experiment, because you will not know which one moved the number.
Why bigger is rarely proportionally faster
Most jobs are part serial and part parallel. Only the parallel part shrinks when you add cores. Take a ten-minute job on 2 vCPU in which four minutes are serial (checkout, install, a link step) and six can use every core. Doubling the cores halves only the six. The times below are an illustration of the arithmetic, not a measurement of any real workflow. The cost column is our rate card’s own weighting: on one platform a minute is charged in proportion to its vCPU, so a 4 vCPU minute costs two 2 vCPU minutes. The pricing page has the rate per size.
| Size | Time | Time saved | Cost |
|---|---|---|---|
| 2 vCPU | 10 min | 0% | 10 units |
| 4 vCPU | 7 min | 30% | 14 units |
| 8 vCPU | 5.5 min | 45% | 22 units |
| 16 vCPU | 4.75 min | 52.5% | 38 units |
Each doubling saves less time than the one before and costs more than the one before. By 16 vCPU you have bought three quarters of a minute over the 8 vCPU size for sixteen more units. Whether that is worth it depends on what a developer’s minute is worth, which is a decision you can make with the numbers in front of you. The calculator applies the real rates to your own builds. The point is that the right size is usually a stop on the way up, and it is almost never the largest one. The better first move for a job that is mostly serial is to cut the serial part: see How to make GitHub Actions faster.
Case 1: memory, not CPU
Bundlers, JVM builds, large test suites with a database in memory and Docker builds with a heavy install step can all run out of memory long before they run out of CPU. The symptom is an out-of-memory kill, or a job that is fine on a small input and unpredictable on a large one. The sizes differ in memory per vCPU: the Ubuntu x64 sizes give 4 GB at 1 vCPU, 7 GB at 2, then 15, 30 and 60 GB, and 125 GB at 32. ARM gives 4 GB for each vCPU. If a job needs memory and nothing else, the smallest size with enough is the cheapest one, and the table tells you which.
One migration detail follows from this. A runner on GitHub and the label we map it to can differ by a gigabyte, for example 8 GB against 7 GB at 2 vCPU. A job that needs all of it can take the next size up, and when the migration pull request sees a difference it says so beside the move.
Case 2: the disk fills
The disk figure in the table is the root disk, which the operating system and the preinstalled tools share, so it is not all yours. An Ubuntu x64 job from 1 to 16 vCPU starts with at least 38 GB of its 50 free. An ARM job starts with about 65 GB of its 75, and a Windows job with about 30 GB of its 100, or about 20 on the 2022 image, which carries more. If a build of container images, a large dataset or a monorepo checkout fills that, the choices are to clean up as you go, to cache less, or to move to a label with more disk. Among the Ubuntu x64 sizes only 32 vCPU has a larger disk, 400 GB; the ARM labels have 75 GB at every size. The labels page is blunt about it: moving up for the disk’s sake is your choice, because the label costs more for every minute, not only the minutes you needed the disk. Try deleting what you do not need first.
Case 3: a different architecture or platform
A minute on an ARM runner counts as 0.625 of a minute on the Linux x64 runner of the same size, and a minute on Windows counts as 2.5. Those are weights on the same meter. So an ARM job beats the x64 job on cost if it takes less than 1.6 times as long, because 1 divided by 0.625 is 1.6. For work that does not care about architecture, such as a Go or Node test run, it is worth one experiment: same size, ARM label, compare the billed minutes. For a job that builds an image for both architectures it is more than a saving, since each platform can then be built on its own machine rather than emulated; the Docker build cache guide shows the workflow, and the ARM runners page has the sizes.
Windows is the opposite: it costs more per minute, so use it for the jobs that need it and nothing else. It is Windows Server Core, so there is no desktop and no Edge browser, and it cannot run Linux containers, as on GitHub’s own Windows runners. The Windows runners page has the detail.
Size each job, not each workflow
runs-on belongs to a job, so size every job for what it does. The lint job that reads files and prints errors does not need what the compile does, and the deploy job that calls an API needs less still. The cheap saving in many pipelines is not choosing a better size for the build. It is dropping the small jobs to the smallest size.
The 1 vCPU label is the equivalent of GitHub’s ubuntu-slim, and both have one CPU. They differ in what the job is in: GitHub’s runs in a container, and ours is a virtual machine of its own, where nproc reports 1, with 4 GB of memory against GitHub’s 5. It suits the light jobs: linting, labelling, calling an API.
What the priority lane is not
Adding -priority to a label is sometimes mistaken for a faster size. It is not. It is the same machine at twice the rate per minute, and the only thing it changes is which of your queued jobs gets the next free machine. Once the job is running it is exactly as fast as before, and it never stops a job that is already running. If your jobs are slow to start, the guide to why a job waits for a runner is the place to look first.
The time limit is a size question too
A job on our runners may run for 60 minutes. If a build is close to that, a larger size can bring it back under, and so can splitting it into jobs joined with needs. Splitting is usually the better first move, because it also shortens the time to a result and does not raise the rate. The limits page has the rule.
A short procedure
Start every job at the smallest size that has the memory and the disk it needs. Open its page and read CPU, memory and disk for the slowest step. If CPU is saturated and the step is parallel, move one size up and compare Ran for and the billed minutes across a few runs. Stop when the saving no longer covers the extra cost, or when the developer waiting for the result stops noticing. Try the same size on ARM if the job does not care about the architecture. Do this per job, and revisit it when the workflow changes, because a size chosen for last year’s build is guessing about this year’s.