- Why a matrix is useful for multi-version and multi-OS testing.
- How to define matrix axes and reference them in your job.
- How to keep artifacts and other outputs unique per matrix job.
Current single-job workflow (one runner, one Node.js version)
This workflow runs tests on a single runner and one Node.js version:Example Jenkins pipeline (for context)
A Jenkins pipeline that uses Docker agents per stage could look like this:Why use a matrix in GitHub Actions?
If you need to run the same job across multiple Node.js versions and operating systems (for example, Ubuntu, macOS, and Windows), the matrix strategy lets GitHub Actions expand one job into many parallel jobs—one for each combination of matrix axes. Minimal matrix example (from the GitHub Actions docs):Visualizing matrix axes and combinations
| Axis | Example values | Notes |
|---|---|---|
| Node.js versions | [10, 12, 14] | Each value becomes matrix.version in the job |
| Operating systems | [ubuntu-latest, windows-latest] | Use as matrix.os in runs-on |
| Total combinations | N/A | Product of axis lengths (e.g., 3 × 2 = 6) |
Applying matrix strategy to the Solar System workflow
To convert the earlier single-job workflow to run across multiple Node.js versions and OSes:- Define a
strategy.matrixwith the Node.js versions and operating systems you want. - Reference matrix values in
runs-onand in action inputs likenode-version. - Make artifact names unique per-job by including matrix variables (for example,
Mocha-Test-Results-${{ matrix.nodejs-version }}-${{ matrix.operating-system }}).
The total number of parallel jobs equals the product of the lengths of each matrix axis. In the example above you will get 3 Node.js versions × 2 OS = 6 parallel jobs. Monitor concurrency and runner usage as matrices expand.
ubuntu-latest, Node.js 20 on macos-latest, Node.js 21 on each OS, etc.). Each job runs independently and uploads its artifact using the unique name built from matrix variables.
Here is a screenshot showing multiple successful “unit-testing” jobs and their produced artifacts (note the artifact names include the OS/version combinations):

Example: macOS runner header log
Inspecting a runner’s logs (macOS example) helps you confirm the runner image and installed software. Example runner header output:Best practices and tips
- Use
strategy.matrixto cover multiple combinations of versions and OSes. - Reference matrix variables using
runs-on: ${{ matrix.<name> }}and action inputs likenode-version: ${{ matrix.nodejs-version }}. - Ensure artifacts and other per-job outputs include matrix variables in their names to prevent collisions.
- Be mindful of GitHub-hosted runner limits and costs—macOS runners are limited and costlier. Consider self-hosted runners for heavy parallelization.
Expanding matrix axes multiplies job count. Large matrices can quickly exhaust concurrency limits or increase billing. Test with a small matrix first and incrementally add axes.
Quick reference
| Topic | Example |
|---|---|
| Use runs-on with matrix | runs-on: ${{ matrix.operating-system }} |
| Set node version from matrix | node-version: ${{ matrix.nodejs-version }} |
| Unique artifact name | Mocha-Test-Results-${{ matrix.nodejs-version }}-${{ matrix.operating-system }} |
Links and references
- GitHub Actions matrix docs: https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs
- GitHub runner images: https://github.com/actions/runner-images
- Jenkins documentation: https://www.jenkins.io/doc/