Ink&Horizon
HomeBlogTutorialsLanguages
Ink&Horizon— where knowledge meets the horizon —Learn to build exceptional software. Tutorials, guides, and references for developers — from first brushstroke to masterwork.

Learn

  • Blog
  • Tutorials
  • Languages

Company

  • About Us
  • Contact Us
  • Privacy Policy

Account

  • Sign In
  • Register
  • Profile
Ink & Horizon

© 2026 InkAndHorizon. All rights reserved.

Privacy PolicyTerms of Service
Back to Blog
DevOps

GitHub Actions CI/CD: Reusable Workflows, OIDC & Matrix Strategies

Build production CI/CD pipelines with caching, OIDC auth, reusable workflows, and matrix testing strategies

2026-01-28 22 min read
ContentsGitHub Actions ArchitectureProduction CI Pipeline: Lint, Test, BuildMatrix Strategy: Test Across VersionsReusable Workflows: DRY CI/CDOIDC: Keyless Cloud AuthenticationKey Takeaways

GitHub Actions Architecture

GitHub Actions uses YAML workflow files in .github/workflows/ that define automated pipelines triggered by events (push, pull_request, schedule, manual). Each workflow contains jobs that run on virtual machines (runners). Each job contains steps that execute commands or reusable actions.

Key architecture concepts: Jobs run in parallel by default (add needs: for dependencies). Each job runs on a fresh VM (no shared state between jobs unless you use artifacts). Steps within a job share the same filesystem and environment.

GitHub provides hosted runners (Ubuntu, Windows, macOS) with 2-core CPUs and 7GB RAM. For faster builds, use larger runners or self-hosted runners.

Key Takeaways

Workflow file → Jobs (parallel by default) → Steps (sequential).
Each job runs on a fresh VM — no shared state between jobs.
Steps share filesystem within a job.
Events: push, pull_request, schedule, workflow_dispatch (manual).
Hosted runners: Ubuntu (cheapest), Windows, macOS (most expensive).

Production CI Pipeline: Lint, Test, Build

A production CI pipeline runs on every push and pull request. It validates code quality, runs tests, builds the application, and reports results — all before code reaches the main branch.

The key optimization is dependency caching: without it, every CI run re-downloads all npm packages (30-60 seconds). With caching, subsequent runs restore packages from cache in 2-5 seconds.

Snippet
# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  quality:
    name: Lint & Type Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'  # Caches pnpm store

      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm check-types

  test:
    name: Tests
    runs-on: ubuntu-latest
    needs: quality  # Only run if lint passes
    
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_DB: test_db
          POSTGRES_PASSWORD: test
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with: { version: 9 }
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile

      - name: Run Tests
        run: pnpm test -- --coverage
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/test_db

      - name: Upload Coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with: { version: 9 }
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

Key Takeaways

cache: "pnpm" in setup-node caches the pnpm store automatically.
services: creates containers (PostgreSQL, Redis) for integration tests.
needs: creates job dependencies (test runs after lint passes).
--frozen-lockfile ensures reproducible installs (fails if lockfile is stale).
upload-artifact saves test coverage/build outputs between jobs.

Matrix Strategy: Test Across Versions

Matrix strategies run the same job across multiple configurations simultaneously — Node versions, OS versions, database versions, etc. This ensures your code works across all supported environments without writing duplicate jobs.

The matrix creates a cartesian product of all dimensions. A matrix with 3 Node versions and 2 OS versions creates 6 parallel jobs. Use exclude to skip specific combinations and include to add one-off configurations.

Snippet
jobs:
  test:
    name: Test (Node ${{ matrix.node }}, ${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false  # Don't cancel other jobs if one fails
      matrix:
        node: [20, 22]
        os: [ubuntu-latest, windows-latest]
        exclude:
          - node: 20
            os: windows-latest  # Skip Node 20 on Windows
        include:
          - node: 23
            os: ubuntu-latest
            experimental: true  # One-off: test Node 23 nightly
    
    continue-on-error: ${{ matrix.experimental || false }}
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci
      - run: npm test

Reusable Workflows: DRY CI/CD

Reusable workflows eliminate duplication across repositories. Define a workflow once, call it from any repository. This is how large organizations standardize CI/CD practices across 100+ repositories.

The reusable workflow is defined with workflow_call trigger and can accept inputs and secrets. The caller workflow uses the uses: keyword to reference it.

Snippet
# .github/workflows/reusable-deploy.yml (in shared repo)
name: Reusable Deploy
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      DEPLOY_TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - name: Deploy to ${{ inputs.environment }}
        run: |
          echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"
          # kubectl set image deployment/app app=${{ inputs.image-tag }}

# Caller workflow (in any repo)
# .github/workflows/release.yml
name: Release
on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4
      # ... build and push Docker image

  deploy-staging:
    needs: build
    uses: org/shared-workflows/.github/workflows/reusable-deploy.yml@main
    with:
      environment: staging
      image-tag: ${{ needs.build.outputs.image-tag }}
    secrets:
      DEPLOY_TOKEN: ${{ secrets.STAGING_TOKEN }}

  deploy-production:
    needs: deploy-staging
    uses: org/shared-workflows/.github/workflows/reusable-deploy.yml@main
    with:
      environment: production
      image-tag: ${{ needs.build.outputs.image-tag }}
    secrets:
      DEPLOY_TOKEN: ${{ secrets.PROD_TOKEN }}

Key Takeaways

workflow_call: makes a workflow callable from other workflows.
inputs: typed parameters (string, boolean, number, choice).
secrets: passed explicitly — no ambient secret access.
Pin reusable workflows to a commit SHA for security, not @main.
environment: gates deployment with manual approval and environment secrets.

OIDC: Keyless Cloud Authentication

OIDC (OpenID Connect) eliminates the need for long-lived cloud credentials (AWS keys, GCP service account keys) in your GitHub secrets. Instead, GitHub Actions requests a short-lived token from your cloud provider for each workflow run.

This is more secure because: no long-lived secrets to rotate or leak, tokens expire in minutes, and you can restrict which repositories and branches can assume which cloud roles.

OIDC is supported by AWS, Azure, GCP, HashiCorp Vault, and most cloud providers.

Snippet
# OIDC with AWS — no AWS_ACCESS_KEY_ID needed!
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # Required for OIDC
      contents: read
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure AWS Credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
          aws-region: ap-south-1
          # No access key or secret needed!
      
      - name: Deploy to S3
        run: aws s3 sync ./dist s3://inkandhorizon-prod --delete
      
      - name: Invalidate CloudFront
        run: aws cloudfront create-invalidation --distribution-id E1234 --paths "/*"

Key Takeaways

GitHub Actions CI/CD in 2026 is about: dependency caching (save 30-60s per run), matrix strategies (test across versions), reusable workflows (DRY across repos), OIDC (keyless cloud auth), and service containers (databases in CI).

For interviews: explain the workflow → job → step hierarchy, demonstrate caching patterns, discuss OIDC vs static credentials, and show how reusable workflows work across repositories.

Key Takeaways

Cache dependencies: save 30-60s per CI run with setup-node cache.
Matrix strategy: test across Node versions, OS, databases in parallel.
Reusable workflows: define once, call from any repo. DRY CI/CD.
OIDC: keyless cloud auth — no AWS_ACCESS_KEY_ID needed.
Service containers: spin up PostgreSQL, Redis in CI for integration tests.
fail-fast: false — don't cancel matrix jobs when one fails.
AS
Article Author
Ashutosh
Lead Developer

Related Knowledge

Tutorial

Docker & Kubernetes for Production

5m read
Article

Understanding Closures in JavaScript: The Complete 2026 Guide

22 min read
Article

React 19 Server Components: The Definitive 2026 Guide

28 min read
Article

Next.js 15 App Router Masterclass: Everything You Need to Know

25 min read