Advanced GitHub Actions

Course 3 · Ch 5
Advanced GitHub Actions
Matrix builds, secrets, caching, and a complete deployment pipeline — taking CI from "runs tests" to "ships your project"

Course 2, Chapter 8 covered a single straightforward test workflow. Real projects usually need more: testing across multiple versions at once, using credentials safely, avoiding redundant work on every run, and — the natural endpoint — automatically deploying when everything passes.

Matrix Builds — Testing Multiple Configurations at Once

A matrix runs the same job multiple times with different variable combinations — typically used to test against several language versions or operating systems simultaneously, in parallel, rather than one at a time.

jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: ['18', '20', '22'] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm test

This single definition produces three separate, parallel job runs:

Job
node-version
Result
test (1)
18
✅ Independent
test (2)
20
✅ Independent
test (3)
22
✅ Independent
Matrices can combine multiple dimensions at once
Adding a second array (e.g. os: [ubuntu-latest, windows-latest, macos-latest] alongside node-version) produces every combination automatically — 3 Node versions × 3 operating systems = 9 parallel jobs from a few lines of YAML.

Secrets — Using Credentials Safely in CI

Course 1, Chapter 6 established that secrets never belong in committed code. Actions workflows often genuinely need credentials (a deploy token, an API key for a test run) — GitHub Secrets is the answer: encrypted values stored at the repo or organisation level, injected into workflows at runtime without ever appearing in logs or the YAML file itself.

steps: - name: Deploy to production env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: ./deploy.sh

Set up under Settings → Secrets and variables → Actions → New repository secret. The actual value is never visible again after saving, even to repo admins — only usable inside workflow runs.

Secrets can still leak if you're not careful with what you log
GitHub automatically masks a secret's exact value in logs — but only if the log output matches the secret exactly. Echoing a transformed or partial version of a secret (base64-encoded, concatenated with other text) can bypass this masking entirely. Never deliberately print a secret for "debugging" — find another way to verify it's set correctly.

Caching — Avoiding Redundant Work

Every job starts on a completely fresh virtual machine (Course 2, Chapter 8) — meaning npm install re-downloads every dependency, every single run, unless you cache them.

steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # caches node_modules-equivalent based on lockfile hash - run: npm ci

The cache: 'npm' option (built directly into setup-node) keys the cache to your lockfile's hash — if package-lock.json hasn't changed since the last run, dependencies restore from cache almost instantly instead of re-downloading.

A Complete Deployment Pipeline

Bringing it together — test, then deploy only if tests pass, only on the main branch:

name: CI/CD on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { cache: 'npm' } - run: npm ci && npm test deploy: needs: test # waits for "test" to succeed first runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: ./deploy.sh
needs:
Makes a job wait for another to finish successfully first — the deploy job here only runs if test passes, never in parallel with it.
Branch-scoped trigger
on: push: branches: [main] means this entire pipeline, including deployment, only fires for pushes to main — feature branches just run their own separate test-only workflow if configured.
Most hosting providers have a ready-made deploy action — write deploy.sh only as a last resort
Vercel, Netlify, AWS, and most major platforms publish official Actions in the Marketplace (Course 2, Chapter 8's tip about not reinventing common tasks) — usually a few lines of uses: configuration replaces a hand-written deploy script entirely, and stays maintained as the platform's own deployment process changes.

Reusable Workflows — Avoiding Duplication Across Repos

For teams running near-identical CI across several repositories, a workflow can call another workflow as a reusable building block, rather than copy-pasting the same YAML everywhere.

jobs: call-shared-tests: uses: your-org/shared-workflows/.github/workflows/test.yml@main

Chapter 5 Quick Reference

  • strategy: matrix: runs the same job across multiple parallel variable combinations
  • secrets.<NAME> — encrypted credentials, injected at runtime, masked in logs (but not infallibly — never deliberately print them)
  • cache: 'npm' (or equivalent) — skips redundant dependency downloads, keyed to your lockfile hash
  • needs: — makes one job wait for another to succeed first, the basis of a test-then-deploy pipeline
  • Branch-scoped triggers — restrict deployment-related jobs to main, keeping feature branches test-only
  • Check the Marketplace first for deployment to major hosting providers, before hand-writing a deploy script
  • Reusable workflows — call a shared workflow definition instead of duplicating YAML across repos
  • Next chapter: branch protection, CODEOWNERS, required reviews, and signed commits — securing a serious repo