5 Ways to Reduce Your GitHub Actions Costs by 50%
GitHub Actions is incredibly powerful—and can get expensive fast. After analyzing thousands of workflows across hundreds of repositories, we've identified the most impactful ways to reduce costs.
Here are five changes that can cut your GitHub Actions bill in half.
1. Right-Size Your Runners
Potential savings: 25-50%GitHub offers runners from 2-core to 64-core machines. The pricing scales linearly, but performance doesn't always follow.
Many workflows default to larger runners "just in case," but most jobs don't need them:
| Runner | Cost/min | Best For |
|--------|----------|----------|
| 2-core | $0.008 | Linting, formatting, simple tests |
| 4-core | $0.016 | Unit tests, small builds |
| 8-core | $0.032 | Integration tests, medium builds |
| 16-core | $0.064 | Large builds, E2E tests |
htop or similar)Before: 8-core for everything
runs-on: ubuntu-latest-8-core
After: Right-size per job
jobs:
lint:
runs-on: ubuntu-latest # 2-core is fine
test:
runs-on: ubuntu-latest-4-core # 4-core for tests
build:
runs-on: ubuntu-latest-8-core # 8-core only for build
2. Cache Aggressively
Potential savings: 20-40%Downloading dependencies on every run is slow and expensive. GitHub provides 10GB of cache per repository—use it.
What to cache:- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Pro tip: Use actions/setup-* actions with built-in caching:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Automatic caching!
3. Skip Unnecessary Runs
Potential savings: 15-30%Not every push needs full CI. Smart filtering can dramatically reduce runs:
Skip CI for documentation changes:on:
push:
paths-ignore:
'.md'
'docs/'
'.github/*.md'
Skip CI for draft PRs:
on:
pull_request:
types: [opened, synchronize, ready_for_review]
jobs:
test:
if: github.event.pull_request.draft == false
# ...
Run expensive jobs only on main:
jobs:
unit-tests:
runs-on: ubuntu-latest
# Runs on all branches
e2e-tests:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest-8-core
# Only runs on main branch
4. Optimize Matrix Builds
Potential savings: 30-60%Matrix builds are powerful but can explode in cost. A 4x3x2 matrix is 24 parallel jobs—each one billed separately.
Before:strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [16, 18, 20]
# 9 jobs per push!
After:
strategy:
matrix:
include:
os: ubuntu-latest
node: 20 # Primary target
os: ubuntu-latest
node: 18 # LTS
os: windows-latest
node: 20 # Windows check
# 3 jobs instead of 9
Tips:
fail-fast: true to stop on first failure5. Parallelize Strategically
Potential savings: 10-20%Parallelization can speed up CI—but each parallel job incurs overhead (checkout, setup, teardown). Sometimes running sequentially in one job is actually cheaper.
When to parallelize:Instead of 10 parallel test jobs (10x overhead):
jobs:
test:
runs-on: ubuntu-latest-4-core
steps:
run: npm test -- --parallel --workers=4
# Use runner cores for parallelism, not separate jobs
Bonus: Monitor and Alert
The best optimization is continuous. Set up alerts to catch cost spikes before they become bills:
This is exactly what CICosts does. Try it free →
The Results
Implementing these five changes typically yields:
| Change | Typical Savings |
|--------|-----------------|
| Right-size runners | 25-50% |
| Cache dependencies | 20-40% |
| Skip unnecessary runs | 15-30% |
| Optimize matrices | 30-60% |
| Strategic parallelization | 10-20% |
Combined, teams often see 40-60% reduction in GitHub Actions costs.
Getting Started
---
Want real-time visibility into your GitHub Actions costs? Get started with CICosts →