Github Fundamentals Intermediate

1. What is Matrix Strategy?

A matrix strategy lets you run a single job multiple times using variations of inputs (like different OSs, runtime versions, or build flags). GitHub Actions automatically creates a parallel job for every possible combination of matrix variables.

YAML

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        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 example runs 6 parallel jobs ($2 \text{ OSs} \times 3 \text{ Node versions}$).

2. Explain Caching

Caching stores dependencies (such as node_modules, Maven packages, or Docker layers) across workflow runs. Because runner VMs are completely wiped after every job, downloading dependencies repeatedly wastes time and bandwidth.

You can use actions/cache@v4 or built-in caching options inside setup actions (like actions/setup-node).

YAML

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Built-in caching for NPM dependencies
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci

When a cache key matches, the runner downloads the pre-packaged dependencies instead of reinstalling them from the internet.

3. How Do You Upload Artifacts?

Artifacts are files or directories generated during a job (e.g., compiled binaries, test coverage logs, APKs) that you want to save after the runner VM is destroyed.

You use the actions/upload-artifact@v4 action to attach files to the workflow run.

YAML

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build
      - name: Save build output
        uses: actions/upload-artifact@v4
        with:
          name: web-dist
          path: dist/

Uploaded artifacts can be downloaded manually from the GitHub UI or programmatically by other jobs.

4. How Do Jobs Communicate?

Because every job executes in a completely isolated virtual machine, they do not share filesystem memory or local variables directly. Jobs communicate in two primary ways:

Method A: Job Outputs (For Small Data / Strings)

Pass scalar values (like commit IDs, version numbers, or status flags) via $GITHUB_OUTPUT.

YAML

jobs:
  generator:
    runs-on: ubuntu-latest
    outputs:
      app_version: ${{ steps.set_version.outputs.version }}
    steps:
      - id: set_version
        run: echo "version=1.2.3" >> $GITHUB_OUTPUT

  receiver:
    needs: generator
    runs-on: ubuntu-latest
    steps:
      - run: echo "The version is ${{ needs.generator.outputs.app_version }}"

Method B: Artifacts (For Files / Directories)

Pass files between jobs using actions/upload-artifact in Job 1 and actions/download-artifact in Job 2.

5. Explain Reusable Workflows

Reusable Workflows allow you to avoid duplicating workflow code across repositories or within the same repository. Instead of copying and pasting pipeline definitions, you create a central workflow file and invoke it from caller workflows.

A reusable workflow must use the workflow_call trigger:

YAML

# .github/workflows/reusable-build.yml (Called Workflow)
name: Reusable Build Component
on:
  workflow_call:
    inputs:
      target-env:
        type: string
        required: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building for ${{ inputs.target-env }}"

Caller workflow usage:

YAML

# .github/workflows/main.yml (Caller Workflow)
jobs:
  run-reusable:
    uses: ./.github/workflows/reusable-build.yml
    with:
      target-env: 'production'

6. Difference Between uses and run

Featurerunuses
PurposeExecutes inline shell commands or scripts.Executes a pre-packaged Action or Reusable Workflow.
SourceRuns directly on the runner shell (Bash, PowerShell, zsh).Fetched from GitHub Marketplace, local files, or public repositories.
Examplerun: npm testuses: actions/checkout@v4

7. What is needs?

By default, jobs in a workflow execute in parallel. The needs keyword defines dependency requirements to force sequential execution or form an execution chain (DAG).

YAML

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test

  deploy:
    needs: test # Waits for 'test' job to complete successfully
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

If test fails, deploy is automatically skipped.

8. How Do You Trigger Workflows Conditionally?

You can run workflows, jobs, or individual steps conditionally using if expressions or trigger filters:

Method A: Step or Job Level if Condition

YAML

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Prod
        # Runs only if the event is a push to main branch
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: ./deploy-prod.sh

Method B: Event Path/Branch Filtering (on)

YAML

on:
  push:
    branches:
      - main
    paths:
      - 'src/**' # Triggers only if files in src/ change

9. What are Environments?

Environments describe continuous deployment targets like production, staging, or development. They allow you to attach security controls and configuration variables directly to a deployment target.

Key features of Environments:

  • Required Reviewers: Pause workflow execution until specified team members manually approve.
  • Wait Timers: Delay deployment after a trigger (e.g., wait 15 minutes before pushing).
  • Environment Secrets/Variables: Restrict deployment keys so they are only accessible when running against that specific environment.

YAML

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com
    steps:
      - run: ./deploy.sh

10. What are Concurrency Groups?

Concurrency Groups ensure that only a single job or workflow run within a designated group executes at any given time. If a new run starts while a previous run is in progress, GitHub Actions can automatically cancel the old run or queue the new run.

This prevents deployment race conditions or redundant builds when developers rapidly push multiple commits.

YAML

name: Deploy Pipeline
on: push

# Prevents simultaneous deployments on the same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true # Cancels any previous run still processing

GitHub Actions Advanced Concept

1. What is OIDC Authentication?

OpenID Connect (OIDC) allows GitHub Actions to authenticate directly with cloud providers (AWS, Azure, GCP) using short-lived JWT tokens rather than long-lived, hardcoded credentials or API keys.

  1. The runner requests an OIDC JSON Web Token (JWT) from GitHub’s OIDC provider.
  2. The runner sends this JWT to the cloud provider (e.g., AWS Security Token Service).
  3. The cloud provider validates the signature, evaluates claims (such as repository name, branch, or environment), and exchanges the JWT for short-lived cloud credentials.

2. How Do You Deploy Securely to AWS?

Use OIDC alongside the official aws-actions/configure-aws-credentials action instead of storing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY inside repository secrets.

YAML

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Grants permission to fetch the OIDC JWT token
      contents: read
    steps:
      - uses: actions/checkout@v4
      
      - name: Authenticate with AWS via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
          aws-region: us-east-1

      - name: Deploy AWS Infrastructure
        run: aws s3 sync ./dist s3://my-production-bucket

3. How Do You Optimize Large Workflows?

  • Smart Caching: Cache dependency directories (~/.npm, ~/.m2, target folders) using build-tool-native caching options.
  • Path & Branch Filtering: Use paths and paths-ignore so workflows run only when relevant code changes.
  • Job Dependencies (needs): Fail fast by placing cheap checks (linting, static analysis) before heavy build/test jobs.
  • Matrix Filtering / Cancellation: Set fail-fast: true inside matrix strategies so remaining matrix combinations cancel immediately if one fails.
  • Concurrency Cancellation: Set cancel-in-progress: true inside concurrency blocks to terminate stale builds on new commits.

YAML

on:
  push:
    paths:
      - 'src/**'  # Ignore documentation or root README changes

4. How Do You Reuse Workflows Across Repositories?

Reference a reusable workflow stored in an external repository using the {owner}/{repo}/.github/workflows/{filename}.yml@{ref} syntax. The target repository must have its visibility set to public or shared within an organization.

YAML

# In repo: caller-org/app-repo
jobs:
  call-shared-ci:
    # References shared pipeline from security-team/shared-workflows repo
    uses: security-team/shared-workflows/.github/workflows/ci-template.yml@v2.1
    with:
      environment: 'staging'
    secrets: inherit # Automatically passes caller secrets to called workflow

5. What are Composite Actions?

A Composite Action aggregates multiple step commands or actions into a single reusable action. Unlike reusable workflows (which encompass full jobs), composite actions run as a single step within a job.

They are defined in an action.yml file and require runs.using: 'composite' and an explicit shell: directive on every run step.

YAML

# .github/actions/setup-node-and-cache/action.yml
name: 'Setup Node and Cache'
description: 'Custom setup wrapper for project standard initialization'

inputs:
  node-version:
    default: '20'

runs:
  using: 'composite'
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
    - name: Install dependencies
      shell: bash  # Required in composite actions
      run: npm ci

6. How Do You Secure Secrets?

  • Use Least Privilege: Restrict secret exposure using Environment Secrets gated behind approval environments.
  • Never Print Secrets: Avoid passing secrets directly into shell scripts where set -x or debug flags could print them to standard output.
  • Avoid PR Triggers from Forks: Workflows triggered by pull_request from fork repositories do not receive access to repository secrets by default.
  • Pass Secrets via Environment Variables: Pass secrets to scripts using env blocks rather than raw string replacement inside commands to avoid process listing injection.

YAML

steps:
  - name: Run Secure Script
    env:
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }} # Passed as env var, not inline text
    run: python db_migrate.py

7. What is Least-Privilege Workflow Permission?

By default, GITHUB_TOKEN may inherit read/write permissions depending on organization settings. To enforce least privilege, set global workflow permissions to read-all or contents: read, and grant specific elevated scopes only to the jobs that require them.

YAML

name: Security Standard Pipeline
on: push

# Deny all permissions globally by default
permissions: {}

jobs:
  build_and_test:
    runs-on: ubuntu-latest
    permissions:
      contents: read # Read-only access to code
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  publish_release:
    needs: build_and_test
    runs-on: ubuntu-latest
    permissions:
      contents: write # Granted write access ONLY for release generation
    steps:
      - uses: actions/checkout@v4
      - run: gh release create v1.0.0
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

8. How Do You Handle Rollbacks?

Automate rollbacks using conditional steps (if: failure()) or a dedicated rollback workflow triggered by deployment checks.

YAML

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy Release
        id: deploy_step
        run: ./deploy-app.sh

      - name: Healthcheck Target
        id: healthcheck
        run: ./verify-endpoint.sh

      - name: Trigger Automated Rollback
        if: failure() && steps.deploy_step.outcome == 'success'
        run: ./rollback-to-previous-image.sh

9. How Do You Implement Blue-Green Deployment?

Deploy the new application version to an isolated “Green” environment, perform health checks, and swap live traffic routing (e.g., DNS, Load Balancer, or ingress route) from “Blue” to “Green”.

YAML

jobs:
  blue_green_deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Green Environment
        run: ./deploy-green.sh --version ${{ github.sha }}

      - name: Run Integration Tests Against Green
        run: ./test-target.sh --url https://green.internal.myapp.com

      - name: Switch Traffic (Blue -> Green)
        run: ./switch-load-balancer.sh --target green

10. How Do You Debug Failed Workflows?

  • Enable Runner Diagnostic / Step Debug Logging: Set repository secrets ACTIONS_STEP_DEBUG = true and ACTIONS_RUNNER_DEBUG = true to produce verbose log outputs.
  • Re-run Jobs with Debug Logging Enabled: Click Re-run jobs -> check Enable debug logging from the GitHub UI interface.
  • Inspect Artifacts: Save log files, crash dumps, or screenshots using actions/upload-artifact@v4 inside an if: always() step.

YAML

steps: – name: Run Test Suite run: npm test – name: Capture Failure Logs if: failure() uses: actions/upload-artifact@v4 with: name: test-failure-logs path: ./logs/

Leave a Comment