Skip to content

Matrix Builds

"Does it work on Python 3.11 and 3.12 and 3.13, on Linux and Windows?" A matrix answers by multiplying one job definition into many — no copy-paste.

The basic matrix

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pytest

Check (validated): three jobs spawn — one per Python version — each with matrix.python-version substituted in. One definition, three VMs, zero duplication.

Two dimensions

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    python-version: ["3.12", "3.13"]

Four jobs: every combination (2 × 2). Reference both:

runs-on: ${{ matrix.os }}

Careful: dimensions multiply. Three OSes × four Pythons × two architectures = 24 jobs. That's the point — but watch your minutes on private repos.

Fail-fast and friends

strategy:
  fail-fast: false
  matrix:
    python-version: ["3.11", "3.12", "3.13"]

Default fail-fast: true cancels the other jobs when one fails — fast signal, but you don't learn whether 3.13 also fails. Set false when you want the full picture (typical for version-compatibility matrices).

Excluding combinations

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    python-version: ["3.12", "3.13"]
    exclude:
      - os: windows-latest
        python-version: "3.12"

For the combos you know don't apply (or can't work). There's also include: for one-off additions — like a job with an extra experimental flag.

Naming the jobs

name: test (${{ matrix.os }}, py${{ matrix.python-version }})

Without this, the UI shows "test (1)", "test (2)" — inscrutable. Name them; future-you debugging a red Windows-only failure will be grateful.

Next: Secrets