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
| Feature | run | uses |
| Purpose | Executes inline shell commands or scripts. | Executes a pre-packaged Action or Reusable Workflow. |
| Source | Runs directly on the runner shell (Bash, PowerShell, zsh). | Fetched from GitHub Marketplace, local files, or public repositories. |
| Example | run: npm test | uses: 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.
- The runner requests an OIDC JSON Web Token (JWT) from GitHub’s OIDC provider.
- The runner sends this JWT to the cloud provider (e.g., AWS Security Token Service).
- 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
pathsandpaths-ignoreso 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: trueinside matrix strategies so remaining matrix combinations cancel immediately if one fails. - Concurrency Cancellation: Set
cancel-in-progress: trueinsideconcurrencyblocks 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 -xor debug flags could print them to standard output. - Avoid PR Triggers from Forks: Workflows triggered by
pull_requestfrom fork repositories do not receive access to repository secrets by default. - Pass Secrets via Environment Variables: Pass secrets to scripts using
envblocks 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 = trueandACTIONS_RUNNER_DEBUG = trueto 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@v4inside anif: 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/