Remote Docker Builders for GitHub Actions

A remote Docker builder runs docker build on a dedicated VM with a persistent layer cache outside your GitHub Actions runner. Setup, YAML, and costs.

Last verified:

A remote Docker builder runs docker build on a dedicated machine outside the GitHub Actions runner, with a layer cache that stays on that machine's local disk between jobs. WarpBuild remote Docker builders are created as a builder profile in the dashboard and selected from a workflow with one profile-name input on Warpbuilds/build-push-action.

This guide covers the four cases where a remote builder is the right answer, the setup walkthrough, workflow YAML for both WarpBuild runners and GitHub-hosted runners, and the session billing and sizing arithmetic you need before you roll it out.

Diagnosis

A remote builder changes where the image build happens and where its cache lives. That helps with four specific problems and does nothing for the rest of your workflow, so start by checking which of these your job logs show.

Image builds pinned to runner CPU

When docker build runs inside the job, the build gets whatever vCPU the runner has. BuildKit parallelizes across independent stages, so a Dockerfile with four independent stages has four stages competing for the same 4 vCPU that also has to run checkout, the test steps, and the push.

The usual reaction is to move the whole job to a bigger runner. That works, and it charges the bigger rate for every minute of the job. On the WarpBuild catalog, warp-ubuntu-latest-x64-4x is $0.008 per minute and warp-ubuntu-latest-x64-32x is $0.064 per minute. A 45 minute job that spends 8 minutes in docker build pays $2.88 on the 32x label against $0.36 on the 4x label, and the extra 28 vCPU sit idle for 37 of those 45 minutes.

A builder profile breaks that coupling. The size of the Docker machine has no relation to the size of the GitHub Actions runner; the builder profile you select decides it. So the job keeps a small runner for checkout, tests, and the push, and the image build lands on a machine sized for image builds.

Cache that cannot outlive an ephemeral runner

Cloud runner storage is ephemeral and is deleted when the runner terminates. Any layer cache built during a job disappears with the machine unless the job exports it somewhere.

That is what cache-to: type=gha does: it serializes the layers, uploads them to a cache backend, and the next run downloads and imports them before the build starts. The export and import are real work in the job, and they grow with image size.

A builder profile corresponds to one dedicated builder VM with caching attached to it. Layers stay on that VM's local disk, so the next build reuses them in place with no export and no import step. The builder cache has a TTL of 10 days, and a profile that goes unused for more than 10 days is reset automatically.

Two details matter for correctness. The cache is shared across builds on a profile but is eventually consistent, so layers written by one concurrent build may not be visible to another build running at the same time and will be visible to later builds after synchronization. And you can force a clean state per profile through the API when a bad layer gets pinned.

Multi-architecture builds

Building linux/arm64 from an x64 runner without a native arm64 machine means QEMU emulation for every RUN instruction in the arm64 leg. Compile-heavy images feel that on every build.

Builder profiles handle multi-architecture builds for amd64 and arm64 out of the box. Each architecture runs on a separate builder instance, so a multi-arch build produces one session per architecture rather than one emulated build. The profile has to have both architectures enabled in the dashboard; a profile with only amd64 enabled fails an arm64 build with exec format error.

If you would rather split the matrix across native runners instead of one multi-arch profile, the alternative is a job per architecture on warp-ubuntu-latest-x64-4x and warp-ubuntu-latest-arm64-4x, then a manifest merge step. Multi-platform Docker builds for amd64 and arm64 walks through both shapes, and the Linux ARM64 runner catalog and rates lists the native ARM64 labels and their per-minute prices.

Builds running outside GitHub Actions

A cache that only exists inside GitHub Actions leaves local development and any non-GitHub pipeline rebuilding from scratch. Remote builders are reachable through the API, which covers local development and other CI platforms with the same profile and the same cache.

When to skip a remote builder

Three cases where the added session cost buys you little. First, jobs where the image build is a small share of wall clock, such as a 20 minute test suite followed by a 90 second build of a thin runtime image. Second, workflows that need the built image on the runner filesystem for the very next step, because the image has to travel back over the network from the builder before that step can start. Third, images whose build time is dominated by the push to a registry rather than by the layers themselves.

Fix

The setup is four steps and takes about ten minutes. The full reference lives in the WarpBuild Docker Builders documentation.

1. Create a builder profile. Open the Docker Builders page in the dashboard and create a profile. Give it a name you will reference from YAML, such as api-images. Each builder profile corresponds to one dedicated Docker builder VM with caching, so treat the profile as a long-lived resource that accumulates a cache, and give distinct build workloads their own profiles when their layer sets do not overlap.

2. Pick the size and the architecture set. Size choices run from 16 vCPU, 32GB RAM, 100GB disk up to 192 vCPU, 384GB RAM, 2TB disk. Architecture is part of the profile: arm64 and multi-arch profiles support a maximum size of 64 vCPU, and the 96 vCPU and 192 vCPU sizes are amd64 only. Enable both architectures on the profile if any workflow will build multi-arch from it.

3. Authenticate. The workflow talks to the builder over secure TLS authentication, with the CA and client certificates issued per builder session. On WarpBuild runners the action picks up credentials automatically and no API key is needed. Everywhere else, create an API key from the API keys page with the ci and cache scopes, store it as a repository or organization secret named WARPBUILD_API_KEY, and pass it through the api-key input. The automation API documentation covers key creation and the surrounding endpoints.

4. Reference the profile from the action. Warpbuilds/build-push-action is a drop-in replacement for docker/build-push-action that sets up the remote builder for you. Remove the docker/setup-buildx-action step if it exists only to set up a builder.

-      - name: Setup Buildx
-        uses: docker/setup-buildx-action@v3
-
-      - name: Build and push
-        uses: docker/build-push-action@v6
+      - name: Build and push
+        uses: Warpbuilds/build-push-action@v6
         with:
           context: .
           push: true
           tags: user/app:latest
-          cache-from: type=gha
-          cache-to: type=gha,mode=max
+          profile-name: "api-images"

At run time the action requests a builder assignment for that profile, waits for the builder to report ready, receives the host address plus the CA, client certificate, and client key, creates a buildx node with the remote driver pointed at the builder over TLS, and runs the build there. The post step completes the builder session. Billing runs until the session is completed, which is why the session is closed for you rather than left to the job.

The timeout input controls how long the action waits for the builder to be ready, in milliseconds, and defaults to 10 minutes for build-push-action.

Configuration

On a WarpBuild runner

No API key is required when the job runs on a WarpBuild runner.

name: build-image

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-4x
    steps:
      - uses: actions/checkout@v4

      - name: Log in to the registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: Warpbuilds/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          profile-name: "api-images"

On a GitHub-hosted runner

The same builder profile works from a runner WarpBuild does not operate. The only additions are the api-key input and an explicit timeout.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and push
        uses: Warpbuilds/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          profile-name: "api-images"
          api-key: ${{ secrets.WARPBUILD_API_KEY }}
          timeout: 600000

Multi-architecture from one profile

Set platforms on the action and make sure the profile has both architectures enabled.

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-4x
    steps:
      - uses: actions/checkout@v4

      - name: Build and push multi-arch
        uses: Warpbuilds/build-push-action@v6
        with:
          context: .
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          profile-name: "api-images-multi"

Custom build steps

When the build needs steps that build-push-action does not cover, Warpbuilds/docker-configure sets the builder up and outputs its connection details for your own commands to use. Invoke it immediately before the step that runs the build, because builders have a built-in idle timeout that exists so that idle builders are not charged to you.

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-8x
    steps:
      - uses: actions/checkout@v4

      - name: Configure WarpBuild Docker Builders
        uses: Warpbuilds/docker-configure@v1
        with:
          profile-name: "api-images"
          timeout: 300000

      - name: Build with custom flags
        run: |
          docker buildx build \
            --build-arg GIT_SHA=${{ github.sha }} \
            --secret id=npmrc,src=$HOME/.npmrc \
            --push -t ghcr.io/${{ github.repository }}:${{ github.sha }} .

Warpbuilds/bake-action is the equivalent drop-in for docker/bake-action and takes the same profile-name, api-key, and timeout inputs.

Resetting a profile cache

A profile that has accumulated a bad layer set can be reset through the API rather than by deleting and recreating the profile.

curl -s -X POST \
  -H "Authorization: Bearer $WARPBUILD_API_KEY" \
  https://api.warpbuild.com/api/v1/builder-profiles/$BUILDER_PROFILE_ID/cache/reset

Platform notes

Docker builds are driven from the Linux labels in that catalog; macOS runners do not support nested virtualization and cannot run Docker, so a macOS job cannot host a Docker build with or without a remote builder.

If the image build is one step in a slower pipeline, Docker builds on GitHub Actions with WarpBuild covers how those pieces fit together, and Docker layer caching on GitHub Actions covers the Dockerfile ordering work that makes any cache pay off.

Cost or Time Model

Two prices are in play. The runner is billed per minute at its catalog rate. The builder is billed per session, measured from when the builder action starts until the job completes. Both are charged, because the runner and the builder are two separate, independent resources.

Multiple concurrent jobs that use the same builder profile share one session. That session is billed from the first job's start to the last job's completion, so a profile serving a fan-out of parallel builds is one billable stretch of time rather than one per job.

Builder profile sizes and concurrency

Prices below are the WarpBuild Docker Builder rates. The concurrency column applies the documented recommended minimum of roughly 8 vCPU and 16GB memory per build job. There is no hard limit on concurrent builds per profile; this is the sizing guide, and exceeding it means builds share cores.

Profile sizePrice per minuteArchitecturesConcurrent builds at 8 vCPU and 16GB each
16 vCPU, 32GB RAM, 100GB disk$0.06amd64, arm64, multi2
32 vCPU, 64GB RAM, 200GB disk$0.12amd64, arm64, multi4
64 vCPU, 128GB RAM, 200GB disk$0.24amd64, arm64, multi8
96 vCPU, 192GB RAM, 600GB disk$0.36amd64 only12
96 vCPU, 192GB RAM, 2TB disk$0.52amd64 only12
192 vCPU, 384GB RAM, 600GB disk$0.72amd64 only24
192 vCPU, 384GB RAM, 2TB disk$0.88amd64 only24

Disk is the second sizing input. A profile holding a persistent layer cache for several large images wants headroom above the sum of those image sizes, which is what the 600GB and 2TB variants are for.

Worked model: one service, one image per push

Assumptions, which you should replace with numbers from your own job logs: the job runs 6 minutes end to end on warp-ubuntu-latest-x64-4x, of which the builder session covers 4 minutes, on a 16 vCPU profile.

Line itemRateQuantityCost
Runner warp-ubuntu-latest-x64-4x$0.008 per minute6 minutes$0.048
Builder session, 16 vCPU profile$0.06 per minute4 minutes$0.24
Total per build$0.288
1,000 builds per month$288.00

The runner line is where the GitHub list-price comparison applies: warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB) costs $0.008 per minute against $0.012 per minute for the 4-core Linux larger runner (4 vCPU, 16 GB), which is 33 percent lower list price. GitHub publishes its per-minute rates in the GitHub Actions minute multipliers reference and on the GitHub pricing page; both were checked on 2026-08-13.

Worked model: monorepo fan-out

Twelve services, each with its own image, all built in parallel on one pull request from the same profile. Twelve concurrent builds at 8 vCPU each is 96 vCPU, so the 96 vCPU, 192GB profile at $0.36 per minute is the size that matches the sizing rule.

Assume the runner jobs are 5 minutes each and the shared session spans 9 minutes from the first builder start to the last job completion.

Line itemRateQuantityCost
Runner jobs, 12 x 5 minutes$0.008 per minute60 minutes$0.48
One shared builder session$0.36 per minute9 minutes$3.24
Total per pull request$3.72
200 pull requests per month$744.00

The alternative is a smaller profile with the builds queued behind each other. A 32 vCPU profile at $0.12 per minute sizes for four concurrent builds, so the twelve builds run in three waves and the single shared session runs longer. If that session runs 22 minutes, it costs $2.64 against $3.24, and the pull request waits longer. Rate times session length is the whole trade, and the session length is the number you take from your own logs.

Worked model: multi-architecture

Multi-arch builders create two sessions on the same builder profile, one per architecture, and those sessions are billed independently. Each session stays alive until its post-action steps complete.

On a 16 vCPU multi-arch profile at $0.06 per minute with a 5 minute build per architecture, that is 2 sessions x 5 minutes x $0.06 = $0.60 per build, plus the runner minutes. Budget multi-arch as double the single-arch session cost before you compare it against a matrix of native per-architecture jobs.

Reading the bill

Docker Builder usage has its own tab under Reports, separate from GitHub Actions runner and cache usage. It shows per-session cost data: a daily stacked bar chart grouped by profile or architecture, summary cards for total cost and total sessions, and a table where each row is a single builder session with its profile, architecture, duration, and cost. Filter by profile and architecture to find the profile that is running long sessions for short builds, which is the signature of a builder that is being held open by a step placed after the build.

Nothing about this needs a plan change.

FAQ

Do I pay for both the GitHub Actions runner and the Docker builder?

Yes. The runner and the builder are two separate resources and both are billed. The runner is billed per minute at its catalog rate and the builder is billed per session, measured from when the builder action starts until the job completes.

How many concurrent builds fit on one builder profile?

There is no limit on the number of builds that can run concurrently on a builder profile. The documented recommended minimum is roughly 8 vCPU and 16GB memory per build job, so a 32 vCPU, 64GB profile sizes cleanly for about four concurrent builds.

Do I still need cache-from and cache-to with a remote builder?

No. With WarpBuild Docker Builders the cache-to and cache-from options are not required. A cached builder keeps the layers on its own disk and reuses them for later builds. The builder cache has a TTL of 10 days and resets if the profile goes unused for longer.

Can I use a remote Docker builder from a GitHub-hosted runner?

Yes. The builder works with both WarpBuild runners and non-WarpBuild runners. On a runner that WarpBuild does not operate, pass an API key through the api-key input so the action can request a builder assignment.

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.