# Reducing GitHub Actions Costs URL: /blog/github-actions-cost-reduction Practical guide to reducing GitHub Actions costs with triggers, cancellation, caching, right-sizing, and more --- title: "Reducing GitHub Actions Costs" excerpt: "Practical guide to reducing GitHub Actions costs with triggers, cancellation, caching, right-sizing, and more" description: "Practical guide to reducing GitHub Actions costs with triggers, cancellation, caching, right-sizing, and more" author: surya_oruganti date: "2025-10-20" cover: "/images/blog/github-actions-cost-reduction/cover.png" --- This guide focuses on practical ways to reduce costs while improving reliability and developer UX. It's vendor-neutral and cites primary docs throughout. 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. ```yaml 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](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) - Skip runs with commit messages or workflow inputs: [skipping runs](https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs) 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. ```yaml concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true ``` - Concurrency groups and cancellation: [docs](https://docs.github.com/en/actions/using-jobs/using-concurrency) ### Add timeouts to jobs Set hard ceilings on job time. ```yaml jobs: test: timeout-minutes: 20 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm test ``` - Timeouts via workflow syntax: [docs](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) ### Cache smartly (dependencies and Docker) Caching cuts both time and cost. ```yaml - uses: actions/cache@v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: npm-${{ runner.os }}- ``` - Dependency caching: [docs](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) For Docker image builds, prefer BuildKit/Buildx with the GitHub cache backend: ```yaml - 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 ``` - Buildx cache with `type=gha`: [Docker docs](https://docs.docker.com/build/ci/github-actions/cache/) ### 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. ```yaml - 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](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) ### 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. 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. ```yaml strategy: matrix: node: [18, 20, 22] max-parallel: 2 ``` - Matrix usage and limits (256 jobs): [docs](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) ### Observe and budget Track performance and spend so you can right-size and de-fanout with confidence. ```bash # Org billing summary (requires org admin) gh api \ -H "Accept: application/vnd.github+json" \ /orgs/OWNER/settings/billing/actions | jq # A run's billable minutes and timing gh api \ -H "Accept: application/vnd.github+json" \ /repos/OWNER/REPO/actions/runs/123456/timing | jq # Repository artifact sizes gh api \ -H "Accept: application/vnd.github+json" \ /repos/OWNER/REPO/actions/artifacts?per_page=100 | jq '.artifacts[] | {name, size_in_bytes, expired}' # Cache usage for a repo gh api \ -H "Accept: application/vnd.github+json" \ /repos/OWNER/REPO/actions/cache/usage | jq ```

Track: total minutes/spend, median run time for critical workflows, queue time, artifact GB.

### 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](https://docs.github.com/en/billing/managing-billing-for-github-actions/viewing-your-github-actions-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: ```mermaid flowchart LR S[Job slow?] -->|Yes| CPU{CPU >80%?} CPU -- Yes --> SizeUp[Use larger runner] CPU -- No --> IO{IO wait/high net?} IO -- Yes --> BiggerDisk[Use faster disk/network] IO -- No --> Fanout[Reduce matrix fanout] S -->|No| Keep[Keep size] ``` 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

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

See: Playwright retries docs

```yaml # Playwright config use: retries: 2 workers: 4 ```

See: pytest-rerunfailures

```bash # Pytest example pytest -q --maxfail=1 # fail fast pytest --reruns 2 --only-rerun "AssertionError" ```
- Re-running jobs/workflows: [docs](https://docs.github.com/en/actions/managing-workflow-runs/re-running-workflows-and-jobs) ### 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. Compared to GitHub hosted runners, self-hosted runners have the potential to save upto 90% on costs. ## Cost-first execution flowchart ```mermaid flowchart TD A[Push/PR] --> B{Paths match?} B -- No --> X[Skip] B -- Yes --> C[Concurrency group] C -->|cancel previous| D[Run CI] D --> E{Cache hit?} E -- Yes --> F[Fast + cheaper] E -- No --> G[Build + save cache] G --> H[Upload minimal artifacts] ``` --- ## How WarpBuild can help This guide is vendor-neutral; if you want managed building blocks that implement many of the above: - Fast cloud runners (Linux, Windows, macOS): [`https://docs.warpbuild.com/ci/cloud-runners/`](https://docs.warpbuild.com/ci/cloud-runners/) - Snapshot runners: `https://docs.warpbuild.com/ci/snapshot-runners/` - Fast cache and Docker Builders: [`https://docs.warpbuild.com/ci/cache/`](https://docs.warpbuild.com/ci/cache/), [`https://docs.warpbuild.com/ci/docker-builders/`](https://docs.warpbuild.com/ci/docker-builders/) - Observability guides: [`https://docs.warpbuild.com/ci/observability/`](https://docs.warpbuild.com/ci/observability/) - Quick start: [`https://docs.warpbuild.com/ci/quick-start/`](https://docs.warpbuild.com/ci/quick-start/) ---