Why are my GitHub Actions builds slow?

GitHub Actions is usually slow because jobs wait for runners, rebuild dependencies, run sequential work, miss caches, or spend too long uploading artifacts. Measure each phase before changing the workflow.

Last updated 2026-09-10

GitHub Actions builds are usually slow for one of six reasons:

  1. Jobs wait for runner capacity.
  2. The runner takes too long to start.
  3. Dependencies download and install from scratch.
  4. Compilation or container builds miss reusable work.
  5. Tests run sequentially or produce one slow shard.
  6. Cache and artifact uploads extend the critical path after the useful work finishes.

Measure those phases separately. "The workflow takes 20 minutes" isn't enough information to choose a fix.

GitHub Actions runners managed by Depot are the fastest first change for most teams because the workflow stays in GitHub Actions. Change the runner label, then inspect step timing, CPU, memory, and cache behavior in Depot to find the next bottleneck.

 jobs:
   test:
-    runs-on: ubuntu-24.04
+    runs-on: depot-ubuntu-24.04

The runners use faster compute, an in-memory disk accelerator, cache transfers up to 1000 MiB/s, and no runner concurrency limit. Every job runs on a fresh, single-tenant virtual machine that's destroyed after the job finishes.

Separate waiting from execution

Start with one representative pull request workflow and record these timestamps:

PhaseMeasure fromMeasure to
QueueJob becomes readyA runner accepts it
StartupRunner provisioning beginsThe first workflow step starts
DependenciesRestore or install step startsDependencies are ready
BuildCompiler or container build startsBuild output is ready
TestsFirst test startsRequired test suites finish
Artifact and cacheUpload beginsJob finishes
End to endPull request event or workflow dispatch is createdEvery required check finishes

Compare p50 and p95 across at least 30 runs. One fast run hides queue spikes, cache misses, and long-tail tests.

The Depot GitHub Actions metrics view keeps step timing, CPU, and memory data after the ephemeral runner is gone. Use it to tell whether a long step is compute bound, memory constrained, idle on the network, or simply doing too much sequential work.

Fix queue and startup time

If jobs spend minutes in queued, the build command isn't the problem. The workflow is waiting for an eligible runner, a dependency, a concurrency group, or an environment gate.

Depot removes runner-capacity queues by provisioning a runner for every ready job without a concurrency limit. It doesn't bypass a needs dependency, a concurrency rule, or an approval. The guide to eliminating GitHub Actions queue times shows how to distinguish those waits.

If runner startup dominates even after the capacity queue is gone, compare the GitHub webhook, Depot allocation, runner registration, and first-step timestamps. GitHub still owns registration and dispatch for managed GitHub Actions runners. Depot CI removes that control-plane dependency and starts jobs in 2 to 3 seconds on pre-warmed sandboxes.

Stop reinstalling the same dependencies

Package-manager setup often becomes the longest step after the runner is faster.

  • Use the cache support in actions/setup-node, actions/setup-python, or actions/setup-java.
  • Key the cache from the lockfile and every input that changes the resolved dependency graph.
  • Cache the package manager's download store, not a mutable working directory.
  • Verify the logs show a real cache hit before calling the cache warm.

Depot automatically routes actions that use the GitHub Actions cache API through Depot Cache. The action syntax stays the same. The guide to speeding up Node.js builds covers package caches, Turborepo, test sharding, and runner sizing in one workflow.

For system packages, browser binaries, SDKs, and other slow setup that an archive cache doesn't represent well, use a Depot CI custom image. Start the job from a known filesystem instead of reconstructing the machine on every run.

Reuse build output at the right layer

Cache the output using the tool that knows when it's valid.

  • For Docker layers, use a persistent BuildKit cache. Depot container builds keep the project cache automatically and share it between local depot build runs and CI.
  • For a TypeScript monorepo, use the task graph and content hashes from Turborepo with Depot Cache.
  • For a polyglot monorepo, use the native remote-cache protocol for Bazel, Gradle, Maven, Pants, sccache, or Turborepo.

Don't use one broad cache key for every kind of build output. A cache hit is useful only when every input that can change the output is part of its identity.

See the guides to faster Docker builds, shared Docker layer caches, and remote caching for polyglot monorepos for the three common cases.

Shorten the test critical path

A runner with 32 CPUs doesn't make a single-threaded test command use 32 CPUs.

Split independent suites into jobs or matrix shards. Use historical JUnit timing data to keep one slow shard from holding up the workflow. Depot can split tests by timing, run every shard on a separate runner, and use the reported durations to rebalance future runs.

Fix flaky tests before adding more shards. More concurrency exposes shared state, reused ports, order dependencies, and weak cleanup. The guide to debugging intermittent CI failures shows how to compare attempts, rerun one job, and inspect the sandbox with SSH.

Keep uploads off the critical path

Cache saves and artifact uploads still count toward job duration. Record restore and save time separately.

  • Upload only files another job or a human will use.
  • Compress once, not once per shard.
  • Give reports and binaries explicit retention instead of uploading the entire workspace.
  • Don't make an optional debug artifact block every required pull request check.

When a cache takes longer to save than the work it avoids, narrow it or move to a tool-native remote cache that stores smaller, content-addressed outputs.

Recheck the full workflow

Make one change at a time and run the same commit through the same cold and warm conditions. Keep queue, startup, execution, reliability, and cost per completed job in the result.

The guide to benchmarking CI platforms provides the controlled workflow and measurement criteria. The goal isn't the fastest isolated build step. It's the shortest reliable path from a code change to every required check passing.

Start building with Depot
in minutes