How to Speed Up GitHub Actions

A GitHub Actions pipeline that reinstalls every dependency from scratch and runs all test suites serially can balloon to 40+ minutes. Two changes fix most of that: cache the dependencies with actions/cache and parallelize the suites with a strategy.matrix.

DevOps Engineerci/cdgithub-actionscaching

Why GitHub Actions pipelines go slow

Two patterns cause most of the pain on a growing Python (or Node) project:

  1. No dependency caching - every job does a fresh pip install -r requirements.txt, downloading and building the same packages on every run.
  2. Serial test suites - unit, integration, and e2e tests queue up inside a single job, each waiting for the one before it to finish.

Both are easy to fix with standard GitHub Actions primitives.

Cache pip dependencies with actions/cache

actions/cache@v4 saves and restores a directory between runs. For pip, cache ~/.cache/pip keyed on the hash of requirements.txt - so the cache is invalidated automatically whenever dependencies change:

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-${{ hashFiles('requirements.txt') }}

On a cache hit, pip install -r requirements.txt becomes a near-instant no-op (packages are already present). On a cache miss the install runs normally and the result is saved for the next run.

Parallelize test suites with strategy.matrix

strategy.matrix spins up one runner per value in the matrix, all at the same time. Replace a single bloated job with a matrixed job:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        suite: [unit, integration, e2e]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: pip-${{ hashFiles('requirements.txt') }}
      - run: pip install -r requirements.txt
      - run: pytest tests/${{ matrix.suite }}/

With three suites and three runners, wall-clock time drops from unit + integration + e2e minutes to max(unit, integration, e2e) minutes. Combined with caching, a 40-minute pipeline commonly reaches 3-5 minutes.

Further improvements

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How do I cache pip in GitHub Actions?

Use actions/cache@v4 with path set to ~/.cache/pip and key set to pip-${{ hashFiles('requirements.txt') }}. On a cache hit, pip install becomes a near-instant no-op because all packages are already present.

How do I run GitHub Actions jobs in parallel?

Use strategy.matrix on the job and list the values to parallelize over (e.g. suite: [unit, integration, e2e]). GitHub spins up one runner per value simultaneously, so wall-clock time drops to the duration of the slowest suite rather than the sum of all suites.

Why is my GitHub Actions pipeline so slow?

The two most common causes are downloading dependencies from scratch on every run (fix: actions/cache) and running all test suites in sequence inside a single job (fix: strategy.matrix to parallelize them across runners).

Keep learning

Fix the Nginx 502 Bad GatewayDevOps projectRecover a Crashed Linux ServiceDevOps projectCorrect a Postgres Port MismatchDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →