runnerhut
Guides
Fixes for the CI problems teams actually hit.
Speed13
How to speed up GitHub ActionsYour pipeline takes longer than anyone wants to wait, and it is not obvious where the time goes.How to find the slow step in a workflowThe pipeline is slow but every individual step looks reasonable.How to reduce GitHub Actions cold startsJobs sit for 30 to 90 seconds before the first step runs.How to diagnose GitHub Actions queue timesJobs are queued for minutes before a runner picks them up.How to make git checkout fasteractions/checkout takes minutes on a large repository.How to run CI on a very large repositoryCheckout, indexing and status reporting all degrade as the repository grows.How to speed up Git LFS checkoutsLFS objects download serially and dominate checkout time.How to eliminate idle time in jobsJobs spend time doing nothing — waiting on a service, a sleep, or a poll loop.How to handle long-running jobsSome jobs run for hours and occasionally hit the six-hour limit.How to run nightly builds efficientlyNightly builds are expensive and nobody reads the results.How to stop CI rerun stormsDevelopers rerun failing jobs repeatedly, multiplying cost and hiding real failures.How to shard tests across a matrixOne long test job dominates the pipeline.How to run only the tests affected by a changeEvery push runs the whole test suite regardless of what changed.
Cost13
How to reduce GitHub Actions costsThe CI bill grows faster than the team and nobody is sure why.How to measure CI cost per pull requestYou know the monthly total but not what a single pull request costs.How to attribute CI cost to teamsCI spend is one line on the invoice and no team feels responsible for it.How to set a CI minutes budgetSpend is discovered at the end of the month rather than controlled during it.When larger runners actually save moneyA larger runner costs more per minute, so it is unclear whether it is cheaper overall.How to cut macOS runner costsmacOS dominates the CI bill.How to reduce CI egress costsData transfer is a large and growing share of the BYOC bill.How to reduce ECR pull costsContainer image pulls generate significant data transfer charges.How to reduce S3 transfer costs in CICache and artifact traffic to S3 is expensive.The real cost of self-hosted runnersSelf-hosted runners look free next to per-minute billing.How to use spot instances for CI runnersOn-demand instances are expensive for workloads that are inherently retryable.How to right-size your runnersRunner sizes were chosen once and never revisited.How to run CI economically on open sourceOpen-source CI is free on GitHub-hosted runners but slow, and self-hosting is unsafe.
Caching16
How to make CI caches actually persistCaches are configured but hit rate is low.How to work around the 10 GB cache limitYour caches exceed 10 GB per repository and start silently evicting.How to share a build cache across repositoriesEach repository maintains its own cache for the same shared dependencies.How to share a Docker cache between jobsEach matrix leg rebuilds the same layers.How to fix Docker cache invalidationDocker layers rebuild even when the relevant files did not change.How to write a cache key that hitsThe cache is configured but misses on every run.How to use ccache in GitHub ActionsC and C++ builds recompile everything on every run.How to use sccache for Rust buildsCargo rebuilds dependencies whenever the target directory cache misses.How to enable the Gradle configuration cacheGradle spends 30 to 60 seconds configuring before compiling anything.How to cache toolchain managersmise, asdf or proto reinstall language runtimes on every run.How to cache NuGet packagesdotnet restore downloads the same packages on every run.How to cache Jest transformsJest re-transforms every file on every run.How to cache vcpkg and Conan packagesC++ dependency managers rebuild packages from source on every run.How to cache Xcode DerivedDataXcode rebuilds the whole project on every CI run.Choosing a buildx cache backendThere are four cache backends and it is unclear which to use.How to keep the base branch cache warmNew feature branches always start with a cold cache.
Docker12
How to set up Docker layer cachingDocker builds take ten minutes because every layer rebuilds.How to optimise a Dockerfile for CISmall code changes trigger long rebuilds.How to use multi-stage Docker buildsImages are large and contain build tooling that should not ship.How to run concurrent Docker builds safelyParallel matrix legs contend and builds slow down or fail.How to use a remote Docker builderThe layer cache is transferred to and from the runner on every build.How to build multi-platform images fastAdding linux/arm64 makes the build take twenty minutes.How to reduce container image sizeImages are hundreds of megabytes and slow to pull in every job.How to pass secrets into a Docker buildYou need a private registry token or SSH key during the build.How to configure Docker builder profilesDifferent images need different builder resources.How to build many images from a monorepoEvery push builds every image in the repository.How to publish to multiple registriesThe same image is built once per registry.How to set up a registry pull-through cacheImage pulls are slow and you keep hitting Docker Hub rate limits.
arm647
How to move CI to arm64 runnersYou deploy to Graviton or Ampere but build on x64.arm64 migration checklistYou want to move to arm64 without breaking the pipeline.Cross-compiling versus native arm64You need arm64 binaries and are unsure whether to cross-compile or build natively.Why QEMU arm64 builds are so slowAdding linux/arm64 to a Docker build makes it twenty times slower.Fixing Node native modules on arm64npm install fails or compiles from source on arm64 runners.Fixing Python wheels on arm64pip install compiles packages from source on arm64.Building on AWS Graviton runnersYou want the Graviton price-performance advantage in CI.
macOS and iOS8
How to cut Xcode build times in CIXcode builds take twenty minutes on every run.How to speed up iOS simulator testsSimulator tests take half an hour.How to handle iOS code signing in CISigning works locally and fails intermittently in CI.How to notarise macOS apps in CINotarisation is slow and occasionally times out.Fixing macOS runner disk exhaustionJobs fail with no space left on device on macOS.How to parallelise iOS CIiOS CI is serial and macOS minutes are expensive.How to upload to TestFlight from CITestFlight uploads are slow and sometimes fail after a successful build.How to pin Xcode versionsA runner image update silently changed the Xcode version and your build changed with it.
Windows5
How to make Windows CI fasterWindows jobs take three times as long as the equivalent Linux jobs.How to cache MSBuild outputEvery build recompiles all projects.Installing Visual Studio components in CIA required VS workload is missing and installing it takes fifteen minutes.How to sign Windows binaries in CIAuthenticode signing needs a certificate that must not be exposed.How to parallelise .NET tests.NET tests run serially and take twenty minutes.
Security6
How to manage secrets in GitHub ActionsSecrets are sprawled across repository settings and nobody knows what is still used.How to run CI runners inside your VPCBuilds need to reach an internal service that is not exposed publicly.How to give runners a static IPA third-party service requires IP allowlisting and hosted runner IPs change constantly.How to run security scans without slowing CIVulnerability scanning adds minutes to every pull request.How to secure your CI supply chainThird-party actions run with access to your repository and secrets.How to harden workflow permissionsEvery workflow runs with write access it does not need.
Observability10
How to collect runner metricsYou cannot tell whether a job is CPU-bound, memory-bound or disk-bound.How to track build duration percentilesAverage build time looks fine but developers still complain.How to export CI traces with OpenTelemetryCI timing data lives in a separate tool from the rest of your observability.How to query CI data programmaticallyYou need CI data in a dashboard or a weekly report.How to run a weekly CI health reportCI degrades gradually and nobody notices until it is bad.How to alert on CI regressionsA change makes CI 40% slower and it goes unnoticed for weeks.How to deal with flaky CI jobsTests fail randomly and people retry until they pass.How to debug a failing workflowA job fails in CI and works everywhere else.How to set CI service levelsNobody agrees on how fast CI should be, so it never gets prioritised.How to expose CI data to AI agentsAgents debugging CI failures only have log text to work with.
Scale28
How to scale self-hosted runnersSelf-hosted runners either queue at peak or sit idle and expensive.How to use runner groupsAny repository can schedule jobs on any runner.How to design a runner label strategyLabels grew organically and nobody knows which to use.Hosted or BYOC: how to chooseYou are unsure whether to use hosted runners or deploy into your own cloud.How to use reusable workflows at scaleFifty repositories have fifty slightly different copies of the same pipeline.How to coordinate workflows across repositoriesA change in one repository needs to trigger builds in others.How to build custom runner imagesEvery job spends minutes installing the same tools.How to roll out base image updates safelyA base image update breaks builds across many repositories at once.How to handle dependency bot CI loadDependabot opens thirty pull requests and CI cost spikes.How to work with concurrency limitsJobs queue and it is not clear which limit is binding.How to use container jobs effectivelyContainer jobs give reproducibility but break caching.How to use service containers properlyTests fail intermittently at startup.How to build once and deploy many timesThe same artefact is rebuilt for every environment.How to structure deployment jobsDeploys are slow, risky and hard to audit.How to run preview environments efficientlyEvery pull request waits on a full environment build.How to structure release buildsRelease builds are slow and occasionally non-reproducible.How to publish build artifactsArtifact storage costs are growing and retention was never configured.How to handle large artifact uploadsUploading build output takes minutes.How to get incremental builds in CICI always does a clean build even though your build system supports incremental compilation.How to pass data between jobsJobs need to share build output and it is unclear how.How to use matrix builds wellThe matrix has grown and nobody remembers why each combination exists.How to control matrix explosionA small matrix change tripled the job count and the bill.How to size runners for Java buildsYou are guessing at a runner size for a Maven or Gradle build.How to size runners for Node test suitesJest or Vitest workers are either starving or thrashing and you are not sure which.How to size runners for Python test suitespytest is running on one core while the runner has eight idle.How to size runners for Rust buildsCargo builds scale with cores, but it is unclear how far that goes.How to size runners for Docker buildsYou are choosing a runner size for image builds.How to size runners for Android buildsGradle assembles and instrumentation tests need very different machines.