GitHub Actions interview questions covering workflows, jobs, steps, runners, secrets, matrices, caching, artifacts, deployments, and CI/CD.
GitHub Actions is GitHub built-in automation and CI/CD platform. It runs workflows in response to repository events such as pushes, pull requests, releases, schedules, manual dispatches, or external repository dispatch events. Teams use it to run tests, lint code, build artifacts, scan security issues, publish packages, deploy applications, and automate repository tasks.
A workflow is an automation file stored under .github/workflows. It is written in YAML and defines triggers, jobs, permissions, environments, and steps. A repository can have multiple workflows, such as CI, release, deploy, code scanning, and nightly maintenance.
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
Events are activities that trigger workflows. Common events include push, pull_request, workflow_dispatch, schedule, release, issues, and repository_dispatch. Choosing the right event matters because it affects security, timing, branch context, and available secrets.
A job is a group of steps that runs on the same runner. A step is an individual command or action inside a job. Jobs can run in parallel by default, while steps inside a job run sequentially.
A runner is the machine that executes workflow jobs. GitHub-hosted runners are managed by GitHub and available for common operating systems. Self-hosted runners are managed by your team and can run on your own infrastructure for custom tools, private networks, larger machines, or cost control.
GitHub-hosted runners are ephemeral, maintained by GitHub, and convenient for most CI workloads. Self-hosted runners give more control over hardware, network, software, and cost, but require patching, isolation, scaling, monitoring, and stronger security controls.
Use workflow_dispatch to allow manual runs from the GitHub UI or API. Inputs can collect values such as target environment, version, or dry-run mode. Validate inputs in the workflow because manual controls can affect production systems.
on:
workflow_dispatch:
inputs:
environment:
description: Target environment
required: true
type: choice
options: [staging, production]
pull_request runs workflows when pull requests are opened, synchronized, reopened, or otherwise changed. It is common for CI because it tests code before merge. Be careful with secrets and forked pull requests because untrusted code may run in this context.
push runs workflows when commits or tags are pushed. It is often used for branch CI, release builds, and deployments from protected branches. Use branch and path filters to avoid unnecessary runs.
on:
push:
branches: [main]
paths:
- 'src/**'
- 'package-lock.json'
Scheduled workflows use cron syntax and run on GitHub schedule, usually in UTC. They are useful for nightly tests, dependency checks, cleanup jobs, and reports. They can be delayed during platform load, so do not use them as precise real-time schedulers.
on:
schedule:
- cron: '0 2 * * *'
A matrix build runs the same job across multiple combinations, such as operating systems, language versions, databases, or dependency versions. It improves coverage without duplicating YAML.
strategy:
matrix:
node: [18, 20]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
needs defines job dependencies. A job with needs waits for required jobs to finish and can read their outputs. Use it to enforce order, such as test before build, build before deploy, or plan before apply.
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
Secrets store sensitive values such as API keys, tokens, and credentials. They are encrypted and masked in logs when matched exactly. Secrets should be scoped narrowly, rotated regularly, and avoided for cloud deployments when OIDC federation can provide short-lived credentials.
Variables store non-sensitive configuration values at repository, organization, or environment level. They are useful for regions, service names, feature flags, and reusable settings. Do not put secrets in variables because they are not designed as secret storage.
OIDC federation lets workflows request short-lived cloud credentials without storing long-lived cloud secrets in GitHub. The cloud provider trusts GitHub identity claims such as repository, branch, environment, and workflow, then issues temporary credentials.
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: us-east-1
permissions controls the GITHUB_TOKEN scopes available to a workflow or job. Set least privilege instead of relying on broad defaults. For example, test jobs may only need contents: read, while deployment jobs may need id-token: write.
permissions:
contents: read
pull-requests: write
GITHUB_TOKEN is an automatically generated token for a workflow run. It can interact with the repository according to the permissions block and repository settings. It is safer than a personal token for many tasks because it is short-lived and scoped to the workflow.
Caching stores dependencies or build outputs between workflow runs. It can reduce CI time for package managers and build tools. Cache keys should include lockfiles or dependency versions so stale caches do not hide dependency changes.
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: npm-${{ runner.os }}-
Artifacts are files uploaded from a workflow run for later download or use by another job. They are useful for test reports, coverage files, build packages, screenshots, logs, and deployment bundles.
- uses: actions/upload-artifact@v4
with:
name: test-report
path: reports/junit.xml
A cache speeds future workflow runs and is keyed by dependency or build inputs. An artifact preserves files produced by a specific workflow run. Use cache for dependencies and artifacts for outputs you want to inspect, deploy, or pass between jobs.
A reusable workflow is a workflow called by another workflow using workflow_call. It helps standardize CI, deployment, security scans, or Terraform pipelines across repositories. Version reusable workflows and document required inputs and secrets.
on:
workflow_call:
inputs:
environment:
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo Deploying ${{ inputs.environment }}
A composite action packages multiple workflow steps into a reusable action. It is useful for repeated setup logic inside or across repositories. Reusable workflows are better for whole jobs or pipelines, while composite actions are better for reusable step sequences.
concurrency controls whether multiple workflow runs or jobs with the same group can run at once. It is useful for canceling older pull request runs, preventing parallel deployments to the same environment, or serializing Terraform applies.
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
Environments represent deployment targets such as staging or production. They can have environment-specific secrets, variables, protection rules, required reviewers, and wait timers. They are useful for deployment governance.
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
Environment approvals pause a job before it can access protected environment secrets or continue deployment. Required reviewers can approve or reject the deployment. This is useful for production gates after automated checks pass.
Use job outputs for small strings and artifacts for files. A step writes output to GITHUB_OUTPUT, the job maps it as an output, and downstream jobs read it through needs.
jobs:
build:
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- id: meta
run: echo "version=1.2.3" >> "$GITHUB_OUTPUT"
deploy:
needs: build
steps:
- run: echo ${{ needs.build.outputs.version }}
Service containers run supporting services for a job, such as PostgreSQL, Redis, MySQL, or Selenium. They are useful for integration tests that need real dependencies. Health checks and ports help ensure the service is ready.
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
Filters reduce unnecessary workflow runs by limiting triggers to specific branches, tags, or changed paths. They are useful in monorepos and large repositories, but they must be designed carefully so important checks are not accidentally skipped.
Use Docker build-push actions, authenticate to a registry, tag images with immutable values such as commit SHA or release version, and avoid deploying mutable latest tags by themselves.
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/org/api:${{ github.sha }}
Store test reports as artifacts, publish summaries, and optionally use reporter actions that annotate pull requests. Reports help reviewers diagnose failures without digging through long logs.
- name: Add test summary
run: |
echo "### Test Results" >> "$GITHUB_STEP_SUMMARY"
cat reports/summary.md >> "$GITHUB_STEP_SUMMARY"
Start with the failing job and step logs. Check trigger context, branch filters, permissions, secrets availability, runner image, dependency versions, cache hits, service readiness, and environment protections. Add temporary diagnostic commands carefully without printing secrets.
GitHub Actions exposes files such as GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, and GITHUB_STEP_SUMMARY for setting outputs, environment variables, PATH entries, and job summaries. They are safer than older command syntaxes.
echo "APP_ENV=testing" >> "$GITHUB_ENV"
echo "result=passed" >> "$GITHUB_OUTPUT"
echo "### Done" >> "$GITHUB_STEP_SUMMARY"
actions/checkout checks out repository code onto the runner so later steps can build, test, or package it. Important options include ref, fetch-depth, submodules, and persist-credentials. Use fetch-depth: 0 when a job needs full history, tags, or accurate changelog generation.
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
Setup actions install and configure language runtimes or tools on the runner, such as Node.js, Python, Java, Go, or .NET. Many setup actions also support dependency caching, which reduces workflow runtime when lockfiles do not change.
steps:
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
Secret masking hides configured secret values in logs when exact values appear. It is helpful but not complete protection. Derived secrets, encoded secrets, partial values, files, and third-party tool output may still leak if workflows print sensitive data.
pull_request_target runs in the context of the base repository and can access more trusted permissions and secrets. It is useful for safe metadata operations, but dangerous if it checks out and executes untrusted pull request code. Use it only with strict patterns.
Self-hosted runners can retain files, credentials, network access, and machine state between jobs if not isolated. They are risky for public repositories and untrusted pull requests. Use ephemeral runners, network segmentation, least privilege, patching, and job isolation.
Pin third-party actions to a full commit SHA for maximum supply-chain safety, or at least to a trusted major version when operationally acceptable. Review action source, permissions, and marketplace trust before use.
- uses: actions/checkout@v4
# strongest pinning uses a full commit SHA from the trusted action repository
Third-party actions can execute code in your workflow environment, read files, access tokens permitted to the job, and interact with external services. Use least permissions, pin versions, review source, and prefer trusted or internal actions for sensitive workflows.
Branch protection can require specific workflow checks to pass before merging. This turns CI into a merge gate. Keep required check names stable, avoid flaky tests, and ensure required workflows cannot be bypassed by path filters unintentionally.
Manual approvals force a human checkpoint before sensitive deployments. They are useful when production changes need coordination, incident awareness, or release windows. They should complement automated tests, not replace them.
A release workflow commonly triggers on tags or manual dispatch, builds artifacts, runs final tests, signs or attests artifacts, publishes packages, creates a GitHub release, and deploys if appropriate. Release workflows should avoid mutable inputs and unreviewed source changes.
on:
push:
tags:
- 'v*.*.*'
Failure notifications can be sent through GitHub UI, email, Slack, Teams, PagerDuty, or custom webhooks. Use if: failure() or job status checks to notify only when needed. Avoid noisy alerts that train teams to ignore failures.
- name: Notify on failure
if: failure()
run: ./notify.sh
continue-on-error lets a failing step or matrix job avoid failing the whole workflow. It is useful for experimental checks, non-blocking compatibility tests, or best-effort reports. Do not use it to hide failures in required production checks.
timeout-minutes limits how long a job or step can run. It prevents hung builds from wasting minutes and blocking queues. Set realistic timeouts for tests, deploys, and external tool calls.
jobs:
test:
timeout-minutes: 20
runs-on: ubuntu-latest
Use path filters, caching, parallel jobs, matrix tuning, selective test runs, faster dependencies, reusable setup, concurrency cancellation, smaller artifacts, and right-sized self-hosted runners where appropriate. Measure slow jobs before optimizing blindly.
Common mistakes include broad GITHUB_TOKEN permissions, long-lived cloud secrets, running untrusted pull request code with secrets, unpinned third-party actions, insecure self-hosted runners, printing secrets in logs, and using pull_request_target unsafely.
Common anti-patterns include one giant workflow for everything, copy-pasted YAML across repositories, no branch protection, flaky required checks, deploying from unprotected branches, overusing continue-on-error, weak cache keys, and using latest tags for deployment artifacts.
A strong CI pipeline checks out code, sets up the runtime, restores cache, installs dependencies, runs linting, tests, and uploads reports. It uses least-privilege permissions, clear triggers, stable required checks, and fast feedback for pull requests.
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
A safe deployment workflow builds an immutable artifact, runs tests, uses protected environments, requests short-lived credentials with OIDC, serializes deployment with concurrency, requires approvals for production, and records deployment output for audit and rollback.
name: Deploy
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
concurrency:
group: production-deploy
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
Explore 500+ free tutorials across 20+ languages and frameworks.