GitHub Actions: CI/CD Workflows From First Run to Secure Deployments · GitHub Actions Workflows: Triggers, CI and Matrices

Build Matrices: Many Versions and Operating Systems

Run the same job across several runtimes and operating systems with strategy.matrix, include, exclude and fail-fast.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A test job that runs on three Python versions and two operating systems, with one combination excluded and one extra added.

The idea

A library should work on every version it claims to support. Instead of copying a job for each, use a matrix: GitHub creates one job per combination of the values you list, and each run reads its values from the matrix context.

  • matrix: { os: [...], python: [...] } produces every combination (here 2 x 3 = 6 jobs).
  • exclude removes combinations; include adds extra ones or extra variables.
  • fail-fast (default true) cancels the remaining jobs when one fails. Set it to false to see every failure.
  • max-parallel limits how many jobs run at once.
  • runs-on: ${{ matrix.os }} lets the matrix choose the operating system.

The code

A Python version and OS matrix
name: Test matrix
on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest]
        python: ["3.11", "3.12", "3.13"]
        exclude:
          - os: windows-latest
            python: "3.11"
        include:
          - os: ubuntu-latest
            python: "3.14"
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
          cache: pip
      - run: python --version

Output

actionlint: no problems found
workflow: Test matrix
triggers: push, pull_request
jobs, in the order they run:
  test: runs on ${{ matrix.os }} | matrix of 7 runs | 3 step(s)

Run this on your computer — see “Get Started” for setup.

The summary above shows how many runs the matrix expands to: six combinations, one removed, one added.

Common mistakes

  • Unquoted versions. In YAML 3.10 is the number 3.1. Always quote versions: "3.10".
  • Leaving fail-fast: true when debugging. One failure cancels the others, hiding whether they fail too.
  • A huge matrix. Each combination consumes minutes; test the extremes (oldest and newest) rather than every version.
  • Required checks and matrices. Required checks are per job name, which includes the matrix values; changing the matrix renames the checks.

Key points

  • strategy.matrix creates a job per combination; read values via ${{ matrix.<name> }}.
  • include and exclude adjust the combinations.
  • Quote version numbers and consider fail-fast: false. Further reading: GitHub Docs.

Practice questions

Why must `python: [3.10]` be written as `["3.10"]`?

Answer

YAML parses an unquoted 3.10 as the number 3.1, so the job would request Python 3.1. Quoting keeps it as the string "3.10".

What does `fail-fast: false` change?

Answer

By default a failing matrix job cancels the still-running ones. With fail-fast false, every combination runs to completion so you see all failures.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?