Aim for pull request checks under ten minutes end to end, and for lint, format and type checks under about ninety seconds so they can run as a fast first stage that fails cheaply. The reason is behavioural rather than technical. Under ten minutes, a developer waits for the result and fixes the failure with the change still in their head. Past roughly twenty minutes they context-switch, and a 45-minute pipeline produces batched pushes, "let CI figure it out" commits, and merges based on a green tick from three commits ago.
Measure the p95 duration, not the mean. The mean hides the retries, the cold caches and the one job that pulls a 2 GB image. The p95 is what people actually experience on a bad afternoon.
On GitHub Actions, public repositories get unlimited free minutes on standard runners. Private repositories on the Free plan get 2,000 included minutes per month plus 500 MB of storage; Pro and Team plans include more. The multipliers are where budgets die: Linux bills at 1x, Windows at 2x, and macOS at 10x. A macOS job is not twice as expensive as Linux, it is an order of magnitude, so an iOS build matrix of four configurations running on every push will consume a monthly allowance in days.
Three levers, in order of payoff. Keep everything on Linux and reserve macOS for jobs that genuinely need Xcode. Cut the matrix on pull requests and run it in full nightly. Use larger runners only where a job is CPU-bound, because a 4-core runner billing at 2x that halves a 20-minute job is break-even at best.
Self-hosted runners remove per-minute billing and keep warm caches and Docker layers on local disk, which often matters more than the compute. You take on patching, autoscaling, disks that fill with dangling images, and the risk of running untrusted fork code inside your network. The crossover is not a minute count, it is whether someone owns the fleet.
A dependency cache that misses is worse than no cache, because you pay download, install and upload. Track hit rate; below roughly 80 percent your key is wrong. Key on the lockfile hash, not the branch: a key built from the OS plus a hash of pnpm-lock.yaml changes only when dependencies change, which is what you want.
Install with the lockfile-respecting command every time: npm ci, pnpm install --frozen-lockfile, yarn install --immutable, pip install -r requirements.txt --require-hashes. These fail loudly when the lockfile is stale instead of quietly resolving a different tree than the developer had.
For Docker, order layers by rate of change: base image, system packages, manifest files, RUN install, application source last. Copying the whole source before installing dependencies invalidates the install layer on every commit, and is the most common reason a 40-second build takes six minutes. With BuildKit, --cache-from against a registry image restores that across ephemeral runners.
Test time scales down almost linearly with shards until fixed overhead dominates. If each job spends 90 seconds checking out, installing and booting a database, sixteen shards of a four-minute suite spend more time on setup than on tests. Four to eight shards is the usual sweet spot; split by recorded timing rather than by file count so shards finish together.
Set an explicit flake budget. A suite where 1 in 100 runs fails spuriously is tolerable. At 1 in 20, engineers reflexively hit rerun and a genuine regression gets rerun straight through to main. Quarantine flaky tests into a non-blocking job with a named owner rather than adding a blanket retry, because blanket retries hide real race conditions in product code.
The DORA research programme settled on four measures: deployment frequency, lead time for changes, change failure rate, and time to restore service. Teams get grouped into performance bands, with the top band deploying on demand, moving a commit to production in under a day, restoring service within an hour, and keeping change failure rate low. The exact band thresholds shift between annual reports, so treat the direction as the signal rather than memorising a cutoff.
The pairing is the useful part. Speed and stability move together in high performers, which kills the argument that shipping less often is safer. If deployment frequency improves and change failure rate climbs, you removed a control you needed. All four are cheap to derive from data your deployment and incident tooling already holds.
Build artifacts and logs default to 90-day retention on GitHub Actions and count against paid storage on private repos. Nobody debugs a three-month-old test screenshot. Drop routine artifacts to 7 to 14 days, keep release artifacts longer deliberately, and stop uploading whole node_modules or coverage HTML on every run. Cheapest bill reduction available, one line of YAML.
Free tools, guides, and resources across the SPUNK13 network.
Visit spunk.bet400+ Free Tools