Test, build, and share data
Use a purposeful matrix
Test supported runtime or operating-system combinations without multiplying low-value jobs.
A matrix runs the same job with different inputs. Node 20 and Node 22. Ubuntu and macOS. Each combination is a separate job.
That is powerful and expensive. Every cell burns minutes.
Tie each axis to a support promise
Write your support policy first:
- “We support Node 20 and 22 on Linux”
- “Production runs Node 22 only”
Then derive the matrix from that policy, not from every runner image GitHub offers.
jobs:
test:
strategy:
matrix:
node: [20, 22]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci && npm test
Two jobs. Both test a documented Node version on Linux.
Matrix jobs multiply billable minutes. Ten cells that run the same lint command on ten identical images teach you nothing new.
Use fail-fast: false when you want every cell to finish before you triage. Use fail-fast: true when the first red cell is enough to block merge.
Keep one authoritative build
Do not matrix the production build across four Node versions if you ship from one version.
Run the wide matrix on tests. Run npm run build once on the version you deploy.
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm ci && npm run build
Use include and exclude when one combination needs extra steps, not when you want random coverage.
Drop axes that repeat the same evidence
Testing Node 20 on Ubuntu and Node 20 on Windows might matter for a CLI tool. For a static site that only deploys to Linux, the Windows cell adds cost without new information.
When you drop an axis, note the support policy change in the changelog so support knows what you no longer test in CI.
Try this on your own project: remove one matrix axis, run the workflow, and note whether you lost any signal you actually use for release decisions.
Lesson completed