WarpBuild LogoWarpBuild

Reducing GitHub Actions Costs

Practical guide to reducing GitHub Actions costs with triggers, cancellation, caching, right-sizing, and more

Reducing GitHub Actions Costs cover

This guide focuses on practical ways to reduce costs while improving reliability and developer UX. It's vendor-neutral and cites primary docs throughout.

Note

Cost is largely a function of billed minutes, runner SKU, artifact/storage usage, and fan-out. Understanding billing and usage is the first step: see About billing for GitHub Actions and Viewing your Actions usage.

Cost Optimizations

Run less by default (event filters)

Only run workflows when it matters.

on:
  pull_request:
    branches: ["main", "release/*"]
    paths: ["app/**", "lib/**", "package.json"]
    paths-ignore: ["**/*.md", "docs/**", "*.png", "*.svg"]
  push:
    branches: ["main"]
  • See workflow syntax for paths/paths-ignore: docs
  • Skip runs with commit messages or workflow inputs: skipping runs

Tip

Use separate lightweight linters for every push and run heavy integration tests only for PRs targeting main.

Cancel superseded work

Stop old runs when new commits arrive.

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
  • Concurrency groups and cancellation: docs

Add timeouts to jobs

Set hard ceilings on job time.

jobs:
  test:
    timeout-minutes: 20
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test
  • Timeouts via workflow syntax: docs

Cache smartly (dependencies and Docker)

Caching cuts both time and cost.

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: npm-${{ runner.os }}-
  • Dependency caching: docs

For Docker image builds, prefer BuildKit/Buildx with the GitHub cache backend:

- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
  with:
    push: false
    cache-from: type=gha
    cache-to: type=gha,mode=max

Store less, keep it shorter (artifacts)

Artifacts are billed for storage and egress. Upload only what you need, compress appropriately, and reduce retention.

Example savings: A team uploading 5GB of artifacts daily with default 90-day retention stores 450GB/month. Reducing retention to 7 days cuts this to 35GB-a 92% reduction. At GitHub's storage rates ($0.008/GB/day for Actions), this saves ~$100/month.

- uses: actions/upload-artifact@v4
  with:
    name: reports
    path: reports/**
    retention-days: 7  # default is 90 days
    compression-level: 6  # balance speed vs size

Quick wins:

  • Reduce retention from 90 to 7-14 days: ~85-90% storage cost reduction

  • Compress artifacts: typical 40-60% size reduction for logs and build outputs

  • Upload only test failures, not full test runs

  • Artifacts and retention: docs

Choose cheaper runners and architectures

Linux runners are cheaper than macOS/Windows. Where possible, move tasks to Linux, and isolate platform-specific steps. Use arm64 runners for builds that are not CPU bound.

GitHub-hosted pricing multipliers (relative to Linux x64):

  • Linux x64: 1x baseline
  • Linux arm64: ~0.5x (50% cheaper)
  • Windows: 2x (2x more expensive)
  • macOS x64: 10x (10x more expensive)
  • macOS arm64 (M1): 3-5x (3-5x more expensive)

Example: A workflow running 1,000 minutes/month on macOS x64 costs ~10x more than the same workflow on Linux. Switching non-macOS-specific tasks to Linux can save $80-90/month per 1,000 minutes at typical GitHub Teams pricing.

Note

Billing differences and entitlements vary by plan and OS: see About billing for GitHub Actions.

Parallelize thoughtfully

Matrix builds are powerful but can explode costs. Keep fan-out intentional and throttle when needed.

strategy:
  matrix:
    node: [18, 20, 22]
  max-parallel: 2
  • Matrix usage and limits (256 jobs): docs

Observe and budget

Track performance and spend so you can right-size and de-fanout with confidence.

Right-size your runners

Use data to pick the smallest runner that meets SLOs.

Example: If a build takes 20 minutes on 2-core and 12 minutes on 8-core, you're paying 4x the rate for a 1.67x speedup-net result is 2.4x more expensive for that run. Right-sizing down from 8-core to 4-core (if 4-core completes in 15 minutes) cuts cost by 2x while only adding 3 minutes. This optimizes for cost but developer experience may suffer.

  • Track job duration trends in the Actions UI and compare before/after changes: usage
  • Watch CPU/memory-bound steps: run smaller/larger machine experiments and choose the knee of the curve
  • Prefer improving IO (cache, network, disk) before simply scaling CPU

Mermaid decision helper:

Tip

To test right-sizing safely, cap max-parallel, then A/B compare durations and queue times before switching your default runner.

Flaky tests quietly tax your bill

Retries and re-runs multiply minutes. Treat flakiness as a cost center.

  • Prefer “retry failed tests only” where supported (keeps cost bounded)
  • Quarantine known-flaky suites and run them on schedule, not per-PR
  • Surface failure triage early; fail fast before long e2e suites

Framework knobs (examples):

See: jest.retryTimes

# Jest
run: npx jest --maxWorkers=50%  # consider also: jest.retryTimes()

See: Playwright retries docs

# Playwright config
use:
  retries: 2
  workers: 4

See: pytest-rerunfailures

# Pytest example
pytest -q --maxfail=1  # fail fast
pytest --reruns 2 --only-rerun "AssertionError"
  • Re-running jobs/workflows: docs

Self-hosted runners can save costs

Self-hosting can reduce per-minute costs (e.g., spot/preemptible instances) and unlock bespoke hardware and caching. It also introduces infra, security, and maintenance overhead.

Optimizing Self-Hosted GitHub Actions Runner Costs

Compared to GitHub hosted runners, self-hosted runners have the potential to save upto 90% on costs.

Cost-first execution flowchart


How WarpBuild can help

This guide is vendor-neutral; if you want managed building blocks that implement many of the above:


Last updated on