GitHub Actions Advanced Workflows: Matrix Strategy, Custom Actions, Cache Optimization, and OIDC Authentication
GitHub Actions has grown far beyond simple CI pipelines. In 2026, teams use it to orchestrate complex multi-platform builds, deploy to cloud providers without long-lived credentials, and package reusable logic in custom actions. This guide dives deep into four advanced topics that separate beginner workflows from production-grade automation.
Matrix Strategy: Testing Across Dimensions
The matrix strategy lets you run a job against multiple combinations of variables with a single YAML block.
Basic Matrix
jobs:
test:
strategy:
matrix:
os: [ubuntu-22.04, windows-2022, macos-14]
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 && npm test
This produces 9 parallel jobs (3 OSes x 3 Node versions) automatically.
Including and Excluding Combinations
strategy:
matrix:
os: [ubuntu-22.04, windows-2022]
node: [18, 20, 22]
include:
- os: ubuntu-22.04
node: 22
experimental: true
exclude:
- os: windows-2022
node: 18
Fail-Fast and Max-Parallel
strategy:
fail-fast: false
max-parallel: 4
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
Splitting a large test suite into shards dramatically reduces total wall-clock time.
Dynamic Matrix from JSON
jobs:
generate-matrix:
runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.set.outputs.matrix }}
steps:
- id: set
run: |
echo 'matrix={"pkg":["api","web","worker"]}' >> $GITHUB_OUTPUT
build:
needs: generate-matrix
strategy:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
runs-on: ubuntu-22.04
steps:
- run: echo "Building ${{ matrix.pkg }}"
Custom Actions: Composite, JavaScript, and Docker
Composite Actions
# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Install deps and restore build cache'
inputs:
node-version:
description: 'Node.js version'
default: '20'
outputs:
cache-hit:
description: 'Whether the cache was restored'
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: composite
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- id: cache
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
shell: bash
JavaScript Actions
// index.js
const core = require('@actions/core');
const github = require('@actions/github');
async function run() {
try {
const token = core.getInput('github-token', { required: true });
const label = core.getInput('label', { required: true });
const octokit = github.getOctokit(token);
const { context } = github;
if (context.eventName !== 'pull_request') {
core.warning('This action only works on pull_request events');
return;
}
await octokit.rest.issues.addLabels({
...context.repo,
issue_number: context.payload.pull_request.number,
labels: [label],
});
core.setOutput('labeled', 'true');
} catch (err) {
core.setFailed(err.message);
}
}
run();
# action.yml for JS action
name: 'Auto Label PR'
runs:
using: node20
main: index.js
inputs:
github-token:
required: true
label:
required: true
Docker Actions
# Dockerfile
FROM python:3.12-slim
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
set -e
INPUT_THRESHOLD="${INPUT_THRESHOLD:-80}"
echo "coverage-pct=${INPUT_THRESHOLD}" >> $GITHUB_OUTPUT
Cache Optimization
actions/cache Fundamentals
- uses: actions/cache@v4
id: cache
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
Language-Specific Caching
Go modules:
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
Python pip:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
Rust cargo:
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
Docker Layer Caching with Buildx
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: registry.example.com/app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
OIDC Authentication: Keyless Cloud Access
Long-lived service account credentials stored as secrets are a security liability. OIDC lets your workflow request short-lived tokens from cloud providers without any stored secrets.
AWS OIDC Setup
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
}
}
}]
}
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-22.04
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: us-east-1
- run: aws s3 sync dist/ s3://my-bucket/
GCP Workload Identity Federation
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-22.04
steps:
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123/locations/global/workloadIdentityPools/github/providers/github
service_account: deploy@my-project.iam.gserviceaccount.com
- uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-service
region: us-central1
image: gcr.io/my-project/app:${{ github.sha }}
Reusable Workflows
# .github/workflows/reusable-test.yml
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
npm-token:
required: true
jobs:
test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- run: npm ci
env:
NPM_TOKEN: ${{ secrets.npm-token }}
- run: npm test
jobs:
run-tests:
uses: ./.github/workflows/reusable-test.yml
with:
environment: staging
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
Concurrency Control
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
Conclusion
Advanced GitHub Actions usage transforms CI/CD from simple test-and-deploy scripts into a sophisticated automation platform. Matrix strategies eliminate redundant workflow definitions. Custom actions promote reuse across repositories. Aggressive caching cuts build times by 50-80%. OIDC eliminates the security risk of long-lived credentials. Together, these techniques let small teams operate at the scale and safety level of much larger organizations.