Sizing Runners and Builders for Docker Builds
Size the job runner for checkout and waiting, then size the remote Docker builder for the layer cache. Two size ladders, a decision rule, and the arithmetic.
Last verified:
For a Docker build on GitHub Actions there are two machines to size, and they answer different questions: the job runner needs enough capacity for checkout, registry login, and whatever else the job does, while the remote Docker builder needs enough capacity for the compile and the layer cache working set. The common shape is a small job runner such as warp-ubuntu-latest-x64-2x at $0.004 per minute paired with a 16 vCPU builder profile at $0.06 per minute, from the WarpBuild pricing page.
Remote Docker builders sit in the WarpBuild product surface alongside snapshot runners, CI observability, an MCP server, and the Action Debugger.
This guide gives you the two size ladders with their rates, the rule for deciding which machine does the building, the workflow configuration for each shape, and the arithmetic for one build at four different shapes. For the wider picture, start at Docker builds on GitHub Actions.
Diagnosis
Before picking a size, work out which resource is actually short. Four measurements from your own job log settle it.
Cold cache, not cores. Read the build log for CACHED prefixes. If a commit that touched one source file still reruns the dependency install and the base image pull, the machine is idle-starved on cache rather than on CPU, and moving from a 4 vCPU runner to a 16 vCPU runner buys you a faster rebuild of work that should not have run at all. That is the case a builder profile fixes.
Disk. WarpBuild Linux runners carry 150GB SSD, and runner storage is ephemeral and deleted when the runner terminates. A multi-stage build of a large image holds the base image, every intermediate stage, and the export at the same time. Builder profiles carry their own disk, from 100GB on the 16 vCPU size to 2TB on the largest amd64 sizes, and that disk holds the persistent cache across runs.
Memory. Parallel compilers scale memory with jobs. If the build dies with an out-of-memory kill at high -j values, the fix is a shape with more RAM rather than more minutes. Every runner size doubles vCPU and RAM together, so RAM per core is constant across the ladder and the only way to buy RAM is to buy cores.
Concurrency on one profile. WarpBuild documents approximately 8 vCPU and 16 GB memory per build job as the recommended minimum for a builder profile. A 16 vCPU, 32 GB profile therefore fits about two concurrent build jobs at that minimum, a 32 vCPU profile about four, and a 64 vCPU profile about eight. Count the builds that overlap on a busy afternoon and size against that number rather than against a single build.
One platform check before you go further: macOS runners do not support nested virtualization and cannot run Docker. Docker jobs run on Linux or Windows runners, and the builder profile is Linux regardless.
Fix
The decision rule is short. Build on the job runner while the layers are small and the cache adds little. Move the build to a builder profile when the cache is the value.
Concretely, keep the build in the job when the image is under about 1 GB, the cold build finishes in about two minutes, one architecture is enough, and fewer than a handful of builds run per day. In that world an in-job buildx instance rebuilds cheaply and a persistent cache saves less than the second machine costs.
Move to a builder profile when any of these hold: the dependency layer takes longer to rebuild than the rest of the build, the image is multiple gigabytes, you build amd64 and arm64 from the same Dockerfile, or several jobs build the same image at once. In that world the cache is the product and the builder is the thing that owns it, because the cache lives on the builder disk between runs while the job runner is reset every job.
The two ladders
Linux x64 job runners, from the cloud runners documentation:
| Runner label | vCPU and RAM | Storage | Per minute | Where it fits |
|---|---|---|---|---|
warp-ubuntu-latest-x64-2x | 2 vCPU, 8 GB | 150GB SSD | $0.004 | Job calls a builder profile and waits |
warp-ubuntu-latest-x64-4x | 4 vCPU, 16 GB | 150GB SSD | $0.008 | Small image built inside the job |
warp-ubuntu-latest-x64-8x | 8 vCPU, 32 GB | 150GB SSD | $0.016 | In-job build at the documented 8 vCPU, 16 GB minimum |
warp-ubuntu-latest-x64-16x | 16 vCPU, 64 GB | 150GB SSD | $0.032 | In-job build plus tests in the same job |
warp-ubuntu-latest-x64-32x | 32 vCPU, 128 GB | 150GB SSD | $0.064 | Rare for one image; use a builder profile instead |
Linux ARM64 runs the same ladder at a lower rate, from warp-ubuntu-latest-arm64-2x at $0.003 per minute to warp-ubuntu-latest-arm64-32x at $0.048 per minute, which makes ARM64 the cheaper choice for the job that only orchestrates a builder.
Remote Docker builder profiles, from the Docker builders documentation:
| Builder size | Disk | Per minute | Architectures | Concurrent build jobs at the documented minimum |
|---|---|---|---|---|
| 16 vCPU, 32 GB | 100GB | $0.06 | amd64, arm64, multi | About 2 |
| 32 vCPU, 64 GB | 200GB | $0.12 | amd64, arm64, multi | About 4 |
| 64 vCPU, 128 GB | 200GB | $0.24 | amd64, arm64, multi | About 8 |
| 96 vCPU, 192 GB | 600GB | $0.36 | amd64 | About 12 |
| 96 vCPU, 192 GB | 2TB | $0.52 | amd64 | About 12 |
| 192 vCPU, 384 GB | 600GB | $0.72 | amd64 | About 24 |
| 192 vCPU, 384 GB | 2TB | $0.88 | amd64 | About 24 |
Pair them by reading across. A 2 vCPU runner with a 16 vCPU profile covers one service image on a busy repository. A 2 vCPU runner with a 32 vCPU profile covers a monorepo where four pull request builds land together. Disk is the second axis: pick the 200GB sizes when one profile serves several large images, because a full disk evicts layers and an evicted layer is a cold layer.
Configuration
The orchestrating shape puts a small runner in front of a builder profile:
name: docker
on:
pull_request:
jobs:
build:
runs-on: warp-ubuntu-latest-x64-2x
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: Warpbuilds/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/acme/api:${{ github.sha }}
profile-name: api-amd64
timeout: 600000The in-job shape keeps buildx inside the job and sizes the runner for the build itself:
jobs:
build:
runs-on: warp-ubuntu-latest-x64-8x
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/acme/api:${{ github.sha }}Four configuration facts change the sizing arithmetic:
- Builder profiles are billed per session, measured from when the builder action starts until the job completes. Jobs that overlap on one profile share a single session, billed from the first job's start to the last job's completion.
- Multi-arch builds run each architecture on a separate builder instance, so one multi-arch profile opens two sessions that are billed independently. Enable both architectures on the profile, or the build fails with
exec format error. - The builder cache has a TTL of 10 days, and a profile unused for longer is reset automatically. A profile that only serves a quiet repository pays the cold path more often than the sizing model suggests.
- Invoke
Warpbuilds/docker-configure@v1immediately before the build step when you drivedocker buildyourself, since billing starts at assignment. Its default wait is 5 minutes, against 10 minutes forWarpbuilds/build-push-action.
Layer ordering decides how much of the cache you get to use at any size. That work belongs in the Dockerfile and is covered in Dockerfile optimization for GitHub Actions.
Cost or Time Model
The durations below are stated assumptions rather than measurements. Replace them with the step times printed in your own job log, then re-run the arithmetic.
Assumptions: a service image of 1.4 GB with seven layers; 5.0 minutes of cold build work that scales with cores and 3.0 minutes of fixed work for the base pull, export, and push that does not; 0.5 minutes of job overhead for checkout and registry login; a warm build on a builder profile that rebuilds the application layer only, at 1.2 minutes with 0.5 minutes of overhead on the job runner.
Cost of one build at four shapes, using the rates in the ladders above:
| Shape | Job minutes | Builder minutes billed | Arithmetic | Cost per build |
|---|---|---|---|---|
A. warp-ubuntu-latest-x64-4x, build in the job, cold cache | 8.5 | 0 | 8.5 x $0.008 | $0.068 |
B. warp-ubuntu-latest-x64-8x, build in the job, cold cache | 6.0 | 0 | 6.0 x $0.016 | $0.096 |
C. warp-ubuntu-latest-x64-2x plus a 16 vCPU profile, warm cache, one job per session | 1.7 | 1.5 | 1.7 x $0.004 + 1.5 x $0.06 | $0.0968 |
| D. Same as C with four jobs sharing one 3.0 minute session | 1.7 | 0.75 per build | 1.7 x $0.004 + 0.75 x $0.06 | $0.0518 |
Three readings come out of that table. Shape B costs more than shape A even though it finishes sooner, because doubling cores doubles the rate while only the 5.0 minutes of scaling work halves. Shape C costs about the same as shape B and returns the pull request in 1.7 minutes instead of 6.0, so the money buys wall clock. Shape D is where the builder becomes the cheaper option outright, because the session cost divides across the builds inside it.
The crossover is arithmetic you can run for your own repository. Builder cost per build is session minutes multiplied by the profile rate, divided by the builds inside the session. At $0.06 per minute, four builds in a 3.0 minute session cost $0.045 each, and eight builds in a 5.0 minute session cost $0.0375 each. Stepping up to the 32 vCPU profile at $0.12 per minute doubles that number, so the larger profile pays for itself only when the extra cores shorten the session or when concurrency exceeds what 16 vCPU covers at the documented per-job minimum.
One caution on shared sessions: the cache is shared but eventually consistent, so a layer produced by one in-flight build may not reach another until it syncs. Later builds pick it up. Batching builds onto one profile lowers the per-build session cost without guaranteeing that simultaneous builds reuse each other's fresh layers.
Both machines appear on the invoice, which is the point of paying for the runner and the Docker builder separately. Every rate on this page comes from the pricing page and was checked on 2026-08-13.
FAQ
What size runner should I use when the Docker build runs on a remote builder?
A 2 vCPU Linux runner is usually enough. The job checks out the repository, authenticates to the registry, calls the builder, and waits, so warp-ubuntu-latest-x64-2x at $0.004 per minute or warp-ubuntu-latest-arm64-2x at $0.003 per minute carries the work. Add cores only if the same job also runs tests or packaging steps.
Does a bigger job runner make the Docker build faster?
Only while the build runs inside the job. Once the build runs on a builder profile, the size of the Docker machine is not limited by or related to the size of the GitHub runner, and the builder profile size is the lever. WarpBuild documents approximately 8 vCPU and 16 GB memory per concurrent build job as the recommended minimum for a builder profile.
Can arm64 and multi-arch profiles use the largest builder sizes?
No. arm64 and multi-arch builder profiles top out at 64 vCPU, and the 96 vCPU and 192 vCPU sizes are amd64 only. Each architecture in a multi-arch build runs on a separate builder instance, so a multi-arch profile opens one billed session per architecture.
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.