GitHub Actions Deep Dive — Workflows, Runners, Matrices, Reusable Workflows & Caching
GitHub Actions automates software workflows directly from your repository. This deep dive covers every major feature: workflow syntax, runner types, build matrices, deployment environments, reusable workflows, composite actions, self-hosted runners, dependency caching, OIDC for cloud authentication, and how to leverage the Actions marketplace.
Learning Path
flowchart LR
A["CI/CD Basics<br/>Build & Test"] --> B["GitHub Actions<br/>This Tutorial"]
B --> C["Reusable Workflows<br/>DRY Pipelines"]
C --> D["OIDC & Deployments<br/>Production CD"]
B --> E["Composite Actions<br/>Custom Steps"]
style B fill:#f90,color:#fff,stroke-width:2px
Workflow Syntax
Every workflow lives in .github/workflows/*.yml:
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 6 * * 1" # 6 AM every Monday
env:
NODE_VERSION: "20"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm testMatrix Builds
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 22
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm testThis generates 8 parallel jobs (3×3 minus 1 exclusion).
Environments and Protection Rules
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
steps:
- run: ./deploy.shConfigure required reviewers, wait timer, and branch limits in the GitHub repo Settings → Environments.
Reusable Workflows
Call a workflow from another workflow:
# .github/workflows/ci.yml (caller)
jobs:
call-test:
uses: ./.github/workflows/test-reusable.yml
with:
node-version: "20"
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}# .github/workflows/test-reusable.yml (reusable)
on:
workflow_call:
inputs:
node-version:
required: true
type: string
secrets:
NPM_TOKEN:
required: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Testing with Node ${{ inputs.node-version }}"Composite Actions
Package multiple steps into one action:
# .github/actions/setup-project/action.yml
name: "Setup Project"
description: "Checkout, install deps, cache"
inputs:
node-version:
default: "20"
runs:
using: "composite"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
shell: bash
- run: npm run build
shell: bashUse it in workflows:
steps:
- uses: ./.github/actions/setup-project
with:
node-version: "22"Caching Dependencies
steps:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-For multiple languages, use dedicated cache actions:
- uses: actions/setup-node@v4
with:
cache: "npm"
- uses: actions/setup-python@v5
with:
cache: "pip"OIDC for Cloud Authentication
jobs:
deploy:
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucketNo long-lived secrets — AWS trusts the OIDC token issued by GitHub.
Self-Hosted Runners
jobs:
build:
runs-on: self-hosted
steps:
- run: make buildInstall the runner on your machine:
# Download and configure
curl -O https://github.com/actions/runner/releases/download/v2.320.0/actions-runner-linux-x64-2.320.0.tar.gz
tar xzf actions-runner-linux-x64-2.320.0.tar.gz
./config.sh --url https://github.com/org/repo --token YOUR_TOKEN
./run.shActions Marketplace
Popular actions from github.com/marketplace:
- uses: docker/login-action@v3
- uses: docker/build-push-action@v6
- uses: docker/metadata-action@v5
- uses: codecov/codecov-action@v5
- uses: actions/upload-artifact@v4
- uses: actions/download-artifact@v4Common Errors
- Incorrect permissions for OIDC — The
id-token: writepermission is required. Without it, OIDC token exchange fails with a 403 error. - Secrets not passed to reusable workflows — Secrets must be explicitly mapped in the caller’s
secrets:block. They are NOT inherited automatically. - Cache miss on every run — The cache key changes when
hashFilesinput changes. Userestore-keyswith a prefix fallback for partial cache hits. - Matrix explosion — A 4×4×4 matrix generates 64 jobs. Use
include/excludeto trim unnecessary combinations. - Self-hosted runner offline — If the runner agent stops or the machine reboots, queued jobs wait indefinitely. Set up the runner as a systemd service for auto-restart.
- Environment protection blocking deployment — If reviewers or wait timers are configured on the environment, the workflow pauses until approved. This is not a bug — it’s the intended safety mechanism.
- Reusable workflow recursion limit — Workflows can call reusable workflows up to 4 levels deep. Beyond that, GitHub rejects the workflow.
workflow_dispatchmissing inputs — If you add inputs toworkflow_dispatchafter the workflow exists, previously queued runs may fail. Always set defaults.
Practice Questions
1. How do you prevent a matrix job from running on Windows with Node 22?
Use the exclude key: exclude: - os: windows-latest\n node: 22
2. What’s the difference between uses: and run: in a step?
uses: calls an action (local, Marketplace, or Docker). run: executes a shell command directly.
3. How do you pass secrets to a reusable workflow?
Map them explicitly: secrets:\n NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
4. What permissions are needed for OIDC authentication?
permissions:\n id-token: write\n contents: read
5. Challenge: Multi-cloud deployment pipeline Write a workflow that: builds on a 3-OS matrix, runs tests with Node 20/22, publishes Docker images to both Docker Hub and ECR, deploys to staging via environment protection rules, and deploys to production after manual approval. Use OIDC for both AWS and Docker Hub authentication.
Mini Project: Monorepo CI with Matrix and Caching
name: Monorepo CI
on:
push:
branches: [main]
pull_request:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.filter.outputs.changes }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
api: ./api/**
web: ./web/**
test:
needs: detect-changes
strategy:
matrix:
package: ${{ fromJSON(needs.detect-changes.outputs.packages) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd ${{ matrix.package }} && npm ci && npm testFAQ
Related Tutorials
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro