Skip to main content

Optimizing Workflows

This guide provides practical strategies to reduce your GitHub Actions costs without sacrificing CI/CD quality.

Quick Wins

1. Right-Size Runners

Many jobs use larger runners than needed:

# Before: 8-core runner for simple tests
runs-on: ubuntu-latest-8-core # $0.032/min

# After: Default 2-core is often enough
runs-on: ubuntu-latest # $0.008/min

Savings: 75% on runner costs

Check if your jobs actually need larger runners by:

  1. Reviewing CPU/memory usage
  2. Testing on smaller runners
  3. Measuring build time difference

2. Cache Dependencies

Avoid downloading packages every run:

- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-

Savings: 2-5 minutes per run

3. Skip Unnecessary Runs

Don't run CI when it's not needed:

on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
- '.gitignore'

Savings: Entire runs for doc-only changes

4. Cancel Redundant Runs

When you push again, cancel the previous run:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

Savings: Prevents paying for superseded builds

Optimize Matrix Builds

Reduce Matrix Size

Only test combinations that matter:

# Before: Full matrix (9 jobs)
strategy:
matrix:
os: [ubuntu, windows, macos]
node: [16, 18, 20]

# After: Smart selection (5 jobs)
strategy:
matrix:
os: [ubuntu]
node: [16, 18, 20]
include:
- os: windows
node: 18
- os: macos
node: 18

Savings: 44% fewer matrix jobs

Use Fail-Fast

Stop the entire matrix when one fails:

strategy:
fail-fast: true
matrix:
os: [ubuntu, windows, macos]

Savings: Avoid running all jobs when there's a known failure

Optimize Job Duration

Parallelize Tests

Run tests in parallel to reduce wall time:

- run: npm test -- --parallel --maxWorkers=4

Split Large Jobs

Break monolithic jobs into parallel smaller ones:

jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- run: npm run test:unit

test-integration:
runs-on: ubuntu-latest
steps:
- run: npm run test:integration

Use Artifacts Strategically

Build once, use many times:

jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/

test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: build
- run: npm run test

Reduce OS Costs

Minimize macOS Usage

macOS is 10x the cost of Linux:

# Only use macOS when necessary
jobs:
test:
runs-on: ubuntu-latest # Most tests

test-ios:
runs-on: macos-latest # Only iOS-specific

Minimize Windows Usage

Windows is 2x the cost of Linux:

# Use Linux with Docker for Windows testing
jobs:
test:
runs-on: ubuntu-latest
container: mcr.microsoft.com/windows/servercore

Schedule Optimization

Reduce Schedule Frequency

Run scheduled jobs less often:

# Before: Every hour
on:
schedule:
- cron: '0 * * * *'

# After: Every 6 hours
on:
schedule:
- cron: '0 */6 * * *'

Savings: 83% reduction in scheduled runs

Use Conditions

Only run expensive steps when needed:

- name: Deploy
if: github.ref == 'refs/heads/main'
run: ./deploy.sh

Workflow Organization

Combine Small Workflows

Merge workflows that always run together:

# Before: 3 separate workflows = 3× job startup overhead
# After: 1 workflow with 3 jobs
jobs:
lint:
...
test:
...
build:
...

Savings: Reduced job startup overhead

Use Reusable Workflows

Avoid duplicating workflow logic:

jobs:
call-workflow:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: 18

Measure Optimization Impact

Before/After Comparison

Use CICosts to measure:

  1. Note current metrics
  2. Implement optimization
  3. Wait for data (1-2 weeks)
  4. Compare in CICosts

Key Metrics to Track

MetricTarget
Cost per runDecrease
DurationDecrease
Success rateMaintain or improve
Cost per PRDecrease

Optimization Checklist

Run through this checklist for each expensive workflow:

  • Is the runner size appropriate?
  • Are dependencies cached?
  • Can we skip runs for non-code changes?
  • Can we reduce matrix size?
  • Are there redundant steps?
  • Can jobs run in parallel?
  • Is macOS/Windows usage minimized?
  • Are scheduled jobs necessary at current frequency?

Next: Setting Budgets →