Do I Need Buildx in GitHub Actions?

Yes for multi-platform images and cross-run cache backends, no for a single-platform build. What buildx adds, and what a remote builder profile removes.

Last verified:

You need buildx for multi-platform images and for cache backends that outlive the job, and you do not need it for a single-platform image built and pushed inside one job. When a remote builder profile is in play, the drop-in build actions configure buildx for you, so the separate docker/setup-buildx-action step and the cache-to and cache-from flags come out of the workflow file.

Answer

On a current Docker Engine, docker build already runs through BuildKit, so the Dockerfile features people associate with BuildKit work without any extra setup: cache mounts, secret mounts, heredocs, and parallel stage execution. That is why the question comes up at all. The step that adds buildx is often copied from an older workflow and left there.

What docker buildx adds is the builder layer above the engine. It creates named builder instances, selects a driver that decides where the build actually executes, exposes cache import and export backends, and assembles manifest lists that cover more than one platform. The buildx repository documents the driver list, including the remote driver that a WarpBuild builder profile uses.

What the job doesBuildx neededReason
Build and push one image for the runner's own architectureNoThe default builder covers it. Layers stay in the local store and disappear with the ephemeral runner.
Build linux/amd64 and linux/arm64 under one tagYesOnly a buildx builder with both platforms available can produce the manifest list.
Reuse layers across runs through cache-to and cache-fromYesThe default docker driver exports inline cache only. Registry, gha, s3, and local backends need a builder created by buildx.
Build several targets from a bake fileYesdocker buildx bake is a buildx subcommand.
Build on a remote builder VMYes, and the action sets it upThe remote driver is a buildx driver, configured by the WarpBuild build actions.

Everything below the first row points at the same conclusion. Once a workflow needs layer reuse or a second architecture, buildx is in the picture, and the only open question is who configures it.

The Docker build job usually sits on a small Linux runner while the image build itself executes on a separate builder VM. The Docker builders documentation is the reference for the behavior described on this page.

Detail

What the drop-in actions remove from the workflow file

Each builder profile corresponds to one dedicated Docker builder virtual machine with a persistent layer cache. Warpbuilds/build-push-action and Warpbuilds/bake-action are drop-in replacements for the upstream Docker actions, and they configure buildx against that builder automatically. The documentation recommends removing the docker/setup-buildx-action step when it exists only to set up builders.

The cache flags go too. When using Docker Builders, the cache-to and cache-from options are not required, because a cached Docker builder caches the layers and reuses them for subsequent builds without being told to.

-      - name: Set up 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: "super-fast-builder"

Here is the whole workflow after the change, building both architectures under one tag:

name: image
on:
  push:
    branches: [main]

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

      - 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: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          platforms: linux/amd64,linux/arm64
          profile-name: "super-fast-builder"
          timeout: 600000

Two details decide whether this workflow runs. The builder profile must have both architectures enabled in the WarpBuild UI, otherwise an arm64 build fails with exec format error. And api-key: ${{ secrets.WARPBUILD_API_KEY }} is required when the job runs on a runner that is not a WarpBuild runner; on WarpBuild runners the key is unnecessary. The builders work from GitHub Actions, other pipelines, and a developer laptop through the same API.

Driving buildx yourself

Teams that already own their build steps do not have to hand them over. Warpbuilds/docker-configure@v1 provisions the builder inside the runner VM and emits its details as action outputs, and every step after it runs against that builder. Place the configure step immediately before the build step, because builders have a built-in idle timeout that exists so nobody pays for a builder that is waiting around.

Outside GitHub Actions the sequence is the documented remote driver setup, and it is worth reading even if you use the actions, because it shows exactly what they automate:

  1. Request a builder assignment from the profile with a POST to /api/v1/builders/assign, passing an external_unique_id for idempotency.
  2. Poll /api/v1/builders/{id}/details until the status is ready, then read the host and the TLS material from the response metadata.
  3. Write ca.pem, cert.pem, and key.pem to a certificate directory.
  4. Create the buildx instance against the remote node.
  5. Build with --builder, then complete the session request and remove the buildx instance.
docker buildx create --name "$BUILDER_NAME" \
  --node "$BUILDER_ID" \
  --driver remote \
  --driver-opt "cacert=$CERT_DIR/ca.pem" \
  --driver-opt "cert=$CERT_DIR/cert.pem" \
  --driver-opt "key=$CERT_DIR/key.pem" \
  --use \
  tcp://$HOST

docker buildx build --builder "$BUILDER_NAME" -t myimage:latest .

Step five carries the money. Billing runs for the entire duration until the assigned builders are terminated, so a script that exits without calling /api/v1/builder-session-requests/complete keeps paying. The actions handle termination in their post-job steps. A hand-rolled script needs a trap.

Appending a second node to the same buildx instance is supported through the same flow, which is how a build distributes across more than one builder.

Builder sizes and what a session costs

Docker builders are billed per session, measured from when the builder action starts until the job completes. Multiple concurrent jobs on the same builder profile share one session, billed from the first job's start to the last job's completion. A multi-arch build runs one session per architecture, so two architectures produce two parallel sessions on the same profile.

Builder profileDiskPrice per minuteArchitectures
16 vCPU, 32 GB RAM100GB$0.06amd64, arm64, multi
32 vCPU, 64 GB RAM200GB$0.12amd64, arm64, multi
64 vCPU, 128 GB RAM200GB$0.24amd64, arm64, multi
96 vCPU, 192 GB RAM600GB$0.36amd64
96 vCPU, 192 GB RAM2TB$0.52amd64
192 vCPU, 384 GB RAM600GB$0.72amd64
192 vCPU, 384 GB RAM2TB$0.88amd64

Rates from the pricing page and the Docker builders documentation, checked on 2026-08-13. The arm64 and multi-arch profiles cap at 64 vCPU; the 96 vCPU and 192 vCPU sizes are amd64 only. The builder cache has a TTL of 10 days, so a profile that goes unused for longer resets and the next build runs cold.

The cost model

The runner and the builder are two separate, independent resources and you are charged for both, so the comparison has to add them. Take the workflow above: a warp-ubuntu-latest-x64-4x runner at $0.008 per minute driving a builder profile, against the same image built inline on the 8-core Linux larger runner at $0.022 per minute, from the GitHub Actions billing reference, checked on 2026-08-13.

Assume the inline build takes 12 minutes, which puts it at $0.264 per build. The builder path costs the combined per-minute rate for as long as the session is open, so the break-even is the point where the session ends.

Builder profileCombined rate per minuteBreak-even against $0.264Share of the 12 minutes
16 vCPU$0.0683.9 minutes32 percent
32 vCPU$0.1282.1 minutes17 percent
64 vCPU$0.2481.1 minutes9 percent

Read the table as a threshold rather than a promise. A 16 vCPU builder pays for itself once the build finishes inside 3.9 minutes, and a 64 vCPU builder has to land inside 1.1 minutes to beat the same baseline. Larger profiles buy parallelism for builds that can use it, and they demand a proportionally larger reduction in wall-clock time to come out ahead on price. Substitute your own inline build duration and rerun the division; the arithmetic is one step.

What moves the wall-clock number is the persistent layer cache on the builder, since a warm builder skips the layers it has already produced. The first build after a profile reset is a cold one, which is the case the 10-day TTL creates.

For cache behavior outside Docker layers, the caching documentation covers the drop-in cache action and the cache-enabled setup actions.

Do I need buildx to build multi-arch images in GitHub Actions?

Yes. A plain build produces one image for one platform. Emitting a manifest list that covers linux/amd64 and linux/arm64 requires a buildx builder with both platforms available. On WarpBuild, arm64 and multi-arch builder profiles cap at 64 vCPU, and a multi-arch build runs one session per architecture, so two architectures bill as two parallel sessions. The multi-arch answer walks through the emulation alternative and why it is slow.

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

No. When using WarpBuild Docker Builders, the cache-to and cache-from options are not required. A cached Docker builder automatically caches the layers and reuses them for subsequent builds, and the builder profile cache resets automatically after 10 unused days. Concurrent builds share the cache with eventual consistency, so a layer produced by one in-flight build may not reach a parallel build until synchronization completes. The buildx cache backends guide compares the backends you would otherwise configure by hand.

When is plain docker build enough?

When the job builds one image for the runner's own architecture, pushes it in the same job, and does not need layer reuse across runs. The default builder handles that, and the layers stay in the local store on an ephemeral runner, so the next run starts cold. That is acceptable for a small image with cheap layers and expensive for anything that compiles. What is a remote Docker builder covers the point where the local store stops being enough.

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

Yes. These are two separate, independent resources and you are charged for both. Builder sessions are billed per minute from the first job's start to the last job's completion, and the size of the builder is set by the profile rather than by the runner it is driven from. The remote Docker builders page lists the profiles, and the pricing page carries every per-minute rate.

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.