Optimize Your CI/CD Pipeline
Get instant insights into your CI/CD performance and costs. Reduce build times by up to 45% and save on infrastructure costs.
Introduction
GitHub Actions provides so-called matrix strategy for running jobs across multiple configurations. You might specify the parameters of jobs, like types of OS or different versions of programming languages your application supports, and a set of jobs can be automatically created with all possible combinations. It is a very effective manner to run multiple tests or builds in parallel so that your application works seamlessly in various environments without redundant configurations.
Steps we will cover in this article:
- What is a Matrix Strategy?
- Single-Dimension Matrix Strategy
- Expanding Matrix Configurations
- Excluding Unwanted Configurations with
exclude
- Dynamic Matrices Techniques
- Handling Failures and Parallel Jobs
What is a Matrix Strategy?
The matrix strategy of GitHub Actions works on the concept of variable definitions that will be used in multiple job runs. For each defined combination, a new job is created; you can basically test or build your project across a variety of different platforms, versions, or settings in one sweep. It saves you from having duplicate workflow files for testing environments.
Example:
Suppose you want to test three versions of Node.js across two operating systems. Here's how you would create a matrix strategy:
jobs:
test-matrix:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
version: [10, 12, 14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.version }}
- run: npm test
In this example, GitHub Actions will automatically create for you six jobs, one for each combination of NodeJS version, 10, 12, and 14, and operating system, Ubuntu and Windows.
Single-Dimension Matrix Strategy
Another use is to define a single variable using a matrix when one only needs to test across different variants of one component, such as a programming language.
Example:
Setup a test to Run a Project with different Node.js versions:
jobs:
test-node-versions:
strategy:
matrix:
version: [10, 12, 14]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.version }}
- run: npm test
In this example, three jobs are created-one for each Node.js version. This will help you make sure your code works as expected on all three versions.
Expanding Matrix Configurations
Sometimes you may want to customize or extend matrix configurations. You may want to add job conditions for some jobs, or add variables. For this reason, the matrix strategy supports two very powerful features: include and exclude.
Adding Custom Configurations with include
The include
keyword allows specifying a job configuration that is not covered by the standard matrix. This is useful in order to test variables and combinations that are out of the standard pattern.
Example:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: windows-latest
node: 16
npm: 6
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- if: ${{ matrix.npm }}
run: npm install -g npm@${{ matrix.npm }}
- run: npm test
This is a configuration setup in addition to some basic matrix, where npm version 6 is only installed when running in Node.js 16 on Windows.
Excluding Unwanted Configurations with exclude
Sometimes certain settings may not apply or even conflict. You can use the exclude
keyword in order to prevent the running of certain job configurations.
Example:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16, 18]
exclude:
- os: windows-latest
node: 18
Here, we exclude the matrix for the combination of windows and nodejs 18, so it will only run five jobs, and it will skip this particular combination.
Dynamic Matrices Techniques
Advanced workflows may want to dynamically build up matrices given the result of a previous step or job. Conveniently, this is useful when either dealing with completely unpredictable configurations or the need to adjust the matrix based on external factors.
Example:
jobs:
define-matrix:
outputs:
colors: ${{ steps.set-colors.outputs.colors }}
steps:
- name: Set Colors
id: set-colors
run: |
echo 'colors=["red", "green", "blue"]' >> "$GITHUB_OUTPUT"
use-matrix:
needs: define-matrix
strategy:
matrix:
color: ${{ fromJSON(needs.define-matrix.outputs.colors) }}
runs-on: ubuntu-latest
steps:
- run: echo "Current color: ${{ matrix.color }}"
For example, here the define-matrix
job produces a list of colors as an output, that the second job then uses to define automatically a matrix, based on that output; this happens to be one of the most flexible and adaptive ways of creating workflows.
Handling Failures and Parallel Jobs
GitHub Actions also has a fail-fast
option on matrix strategies where, if one of the jobs failed, it cancels the rest. This can save so much time and resources when something is wrong at the start of a complex build or deploy process. You can also disable the fail-fast behavior if you don't want it:.
Example:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16, 18]
With the fail-fast
flag set to false
, all jobs will complete, and hence provide a full set of results to study.
Conclusion
GitHub Actions give you the capability to use matrix strategies. That way, you can run multiple jobs in parallel across different configurations. From being on different operating systems to variable programming languages and different environments-execution with matrix strategy saves you time, avoids redundancy, and ensures excellent test coverage. By including a feature like include
, exclude
, and even dynamically generating matrices, you will be able to create agile and robust workflows, well-suited for the particular needs of your project. This approach not only automatically simplifies running multiple tests or builds but also makes your CI/CD pipeline much more effective and reliable.