How to run matrix builds in CI
Use a CI build matrix to test code across multiple language versions and operating systems in parallel, with include/exclude rules and fail-fast control.
What and why
A build matrix runs the same job many times with different parameters, in parallel. It is the standard way to confirm code works across several language versions and operating systems without copying a job definition for each combination. Libraries especially need this: users run them on many runtimes, so the maintainer must test on all of them. A matrix turns a combinatorial testing problem into a few lines of configuration.
The matrix also surfaces version-specific bugs early. A test that passes on the newest runtime but fails on an older supported one is exactly the kind of regression a matrix is designed to catch before release.
Prerequisites
- A CI workflow that already builds and tests your code.
- Code that genuinely supports the multiple runtime versions you list.
- Basic YAML knowledge.
Steps
1. Add a strategy matrix
Under a job, add a strategy.matrix block listing the values to vary:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: ['18', '20', '22']
This expands the single job into three jobs, one per value, each running concurrently. The matrix values appear in the run's job names so failures are easy to attribute.
2. Use matrix values in steps
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
Reference each value with the matrix context. Every expanded job receives its own value, so the same step installs a different runtime in each.
3. Combine multiple dimensions
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: ['20', '22']
runs-on: ${{ matrix.os }}
Multiple keys multiply together. Three operating systems times two runtime versions produce six parallel jobs covering the full cross-product. Setting runs-on from a matrix value is how you test across operating systems.
4. Exclude and include entries
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: ['18', '20']
exclude:
- os: windows-latest
node: '18'
include:
- os: macos-latest
node: '22'
exclude removes a specific combination you do not support, trimming wasted runs. include adds an extra entry that is not part of the cross-product, useful for testing one special case without expanding every dimension.
5. Tune fail-fast and concurrency
strategy:
fail-fast: false
max-parallel: 4
matrix:
node: ['18', '20', '22']
By default fail-fast cancels remaining jobs as soon as one fails. Setting it to false lets every job finish, so you see all failures in one run instead of fixing them one at a time. max-parallel caps how many jobs run at once, which is useful when you have limited runner capacity.
Verification
Push the change and open the run. The single job should expand into one entry per matrix combination, each labeled with its values. Confirm that any excluded combination is absent and any included one is present. Introduce a failure on one version with fail-fast: false and confirm the other versions still complete, demonstrating that you get a full picture of failures.
Next Steps
Generate a dynamic matrix from the JSON output of a prior job when the set of versions is computed rather than fixed. Use matrix values to build and publish platform-specific artifacts. To save minutes, run a small matrix on pull requests and the full matrix only on the main branch or before a release.
Prerequisites
- A working CI workflow
- An app that supports several runtime versions
- Basic YAML knowledge
Steps
- 1Add a strategy matrix
- 2Use matrix values in steps
- 3Combine multiple dimensions
- 4Exclude and include entries
- 5Tune fail-fast and concurrency
- 6Verify all combinations run