GitHub Actions Tutorial — Matrix Basics
Matrix Basics
A matrix strategy defines variables with lists of values. GitHub Actions creates a job for every combination. This is the foundation of cross-platform, multi-version testing.
Defining a Matrix
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
This creates 9 jobs (3 OS × 3 Node versions):
┌──────────────────────┬──────────────┬──────────────┬──────────────┐
│ │ Node 18 │ Node 20 │ Node 22 │
├──────────────────────┼──────────────┼──────────────┼──────────────┤
│ ubuntu-latest │ Job 1 │ Job 2 │ Job 3 │
│ windows-latest │ Job 4 │ Job 5 │ Job 6 │
│ macos-latest │ Job 7 │ Job 8 │ Job 9 │
└──────────────────────┴──────────────┴──────────────┴──────────────┘
All 9 jobs run in parallel (subject to concurrency limits).
How Matrix Variables Work
Matrix variables are available via the matrix context:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python: ['3.10', '3.11', '3.12']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: python --version
Important: Numeric values like 3.10 must be quoted as '3.10' — otherwise YAML parses it as 3.1 (a float).
Single-Dimension Matrix
A matrix can have just one variable:
strategy:
matrix:
version: [14, 16, 18, 20]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.version }}
- run: node --version
This creates 4 parallel jobs, all on the same OS.
Matrix with Extra Variables
You can use matrix values anywhere — environment variables, artifact names, step logic:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20]
runs-on: ${{ matrix.os }}
env:
PLATFORM: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
- uses: actions/upload-artifact@v4
with:
name: results-${{ matrix.os }}-node${{ matrix.node }}
path: ./test-results/
Artifact names must be unique per matrix combination — include matrix values in the name.
Matrix Limits
| Limit | Value |
|---|---|
| Max combinations | 256 per workflow |
| Max concurrent jobs | Depends on plan (20 for Free, 40 for Pro, 60 for Team) |
| Max dimensions | No hard limit (but 256 total combinations) |
Common Mistakes
| Mistake | Fix |
|---|---|
YAML parses 3.10 as 3.1 |
Quote version strings: '3.10' |
| Artifact name collision | Include matrix values in artifact name |
| Too many combinations | Use include/exclude to prune (next topic) |
| Matrix in wrong location | strategy: is at the job level, not step level |
Quick Reference
jobs:
my-job:
strategy:
matrix:
dim1: [val1, val2] # list of values
dim2: [valA, valB] # second dimension
runs-on: ${{ matrix.dim1 }} # use matrix values
steps:
- run: echo "${{ matrix.dim1 }}-${{ matrix.dim2 }}"
| Concept | Detail |
|---|---|
| Total jobs | Product of all dimension sizes |
| Max combinations | 256 |
| Access values | ${{ matrix.<variable> }} |
| Runs in | Parallel (by default) |
← Back to: Chapter 9: Matrix Builds
Next: Include & Exclude →