GitHub Actions Introduction
GitHub Actions is GitHub's native CI/CD engine: event-driven workflows defined in YAML, executed on ephemeral runners (GitHub-hosted or self-hosted), with a token-based permissi…
Introduction
GitHub Actions is GitHub's native CI/CD engine: event-driven workflows defined in YAML, executed on ephemeral runners (GitHub-hosted or self-hosted), with a token-based permissions model that governs what each job can read or mutate in the repository and beyond.
This lesson covers the mental model senior platform engineers use before writing YAML — not button-click tutorials. You will understand how events become workflow runs, how runners isolate (or fail to isolate) workloads, and why the default GITHUB_TOKEN permissions are both convenient and dangerous on public repos.
The story
A fintech startup migrated from CircleCI to GitHub Actions in one weekend. By Monday, a contributor's fork PR exfiltrated an org secret because the workflow ran with write permissions on pull_request instead of the safer pull_request_target split. The platform lead's fix wasn't "disable Actions" — it was tightening the permissions model, moving deploy credentials to OIDC, and requiring workflow changes to pass CODEOWNERS review. That incident is why staff engineers treat Actions as a distributed execution platform with an attack surface, not "free CI."
Understanding the topic
GitHub Actions anatomy — four primitives every staff engineer can whiteboard:
- Events (triggers):
push,pull_request,workflow_dispatch,schedule,release, repository_dispatch, and 30+ others. Filters (branches,paths,types) narrow when workflows fire. - Workflows: YAML files in
.github/workflows/. One repo can have many workflows; each run is tied to a commit SHA and event payload. - Runners: compute where jobs execute. GitHub-hosted (ubuntu/windows/macos, fresh VM per job) vs self-hosted (your VM/K8s pod, persistent state risk). Labels (
runs-on) route jobs. - Permissions model:
permissions:block sets scopes forGITHUB_TOKEN(contents, packages, id-token, etc.). Default changed to read-only in 2023 — explicit elevation required for writes.
Internal architecture
Event → workflow → runner execution path
Git event (push / PR / dispatch)↓Workflow matcher (.github/workflows/*.yml)↓Job queue → runner allocation (hosted or self-hosted)↓Steps: actions/checkout, setup, run, upload-artifact↓GITHUB_TOKEN + secrets (scoped per job/environment)
Visual explanation
Two diagrams show where GitHub Actions Introduction lives in the delivery path and how teams implement it in production.
Step-by-step explanation
- A developer pushes a commit or opens a PR — GitHub evaluates which workflow files match the event filters (
on.push.branches,on.pull_request.paths). - Matching workflows enqueue one or more jobs. Each job requests a runner via
runs-on(e.g.ubuntu-latestor[self-hosted, gpu]). - The runner clones the repo (unless configured otherwise), sets env vars (
GITHUB_SHA,GITHUB_REF), and executes steps — shell commands or reusable Actions from the marketplace. GITHUB_TOKENis injected automatically. Its scopes come from the workflow-levelpermissions:block or repo/org defaults. Writes to contents, packages, or deployments require explicit grants.- Results surface on the commit/PR checks API. Failed required checks block merge when branch protection rules reference them.
Production implementation
Minimal production-ready intro workflow — read-only token, path filters, concurrency guard:
name: CIon:pull_request:branches: [main]paths: ['src/**', 'package.json']push:branches: [main]permissions:contents: readpull-requests: readconcurrency:group: ci-${{ github.ref }}cancel-in-progress: truejobs:verify:runs-on: ubuntu-latesttimeout-minutes: 15steps:- uses: actions/checkout@v4- uses: actions/setup-node@v4with:node-version: 20cache: npm- run: npm ci && npm test
Execution workflow
Map git events to pipeline intent
PR = verify; main push = build artifact; tag = release.
Real-world use
undefined
Enterprise use cases
Org-level governance — large enterprises combine reusable org workflows, required runners, and OIDC for cloud deploy:
Production case study
Case study: A 120-repo SaaS org standardized on GitHub Actions after Jenkins toil consumed 0.5 FTE per quarter.
- Challenge: Jenkins shared agents with root Docker access; plugin upgrades broke pipelines monthly.
- Decision: GitHub-hosted for PR CI; self-hosted K8s runners only for integration tests needing VPC database access.
- Permissions fix: org policy enforced read-only default token; OIDC roles per environment replaced 200 static AWS keys.
- Outcome: median PR feedback dropped from 22 min to 9 min; security audit passed without finding plaintext cloud credentials.
Trade-offs
- GitHub-hosted pros: zero runner maintenance, fresh VM per job, instant scale to thousands of concurrent jobs.
- GitHub-hosted cons: minute billing adds up on heavy matrix builds; no VPC peering to private databases without self-hosted runners.
- Self-hosted pros: custom hardware, private network access, potentially lower $/minute at high volume.
- Self-hosted cons: you own patching, isolation failures, and supply-chain risk if runners persist between jobs.
- Default token: convenient but must be locked down — never grant
contents: writeon untrusted fork PRs.
Security implications
Actions is a code execution platform triggered by git events. Threat model highlights:
- Fork PR attacks: untrusted code runs in a workflow context. Use
pull_request(notpull_request_targetwith secrets) for external contributions; never pass secrets to fork builds. - Pin actions by SHA:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a393ebae42— tags are mutable; supply-chain attacks target @v4 tags. - Least-privilege GITHUB_TOKEN: default read-only; grant write only in deploy jobs on protected branches.
- OIDC over long-lived secrets: cloud IAM roles trust GitHub's OIDC issuer — no AWS keys in repo settings.
- Environment protection rules: required reviewers on
productionenvironment before job starts.
Scalability analysis
At org scale, Actions throughput and cost become platform engineering problems:
- Concurrent job limits: free tier caps parallel jobs; Enterprise raises limits — plan matrix fan-out accordingly.
- Queue latency: macOS and larger runners queue longer during peak hours; critical paths should use Linux unless macOS is required.
- Artifact storage: large test artifacts expire (default 90 days) but count toward storage billing — compress and prune.
- Reusable workflows: centralize CI logic across 500 repos instead of copy-pasting YAML that drifts.
- Runner autoscaling: self-hosted pools on Kubernetes (Actions Runner Controller) scale on queue depth.
Staff engineer insights
- Treat every workflow file as production code — CODEOWNERS on
.github/is non-negotiable at scale. - The event type you choose (
pull_requestvspull_request_target) is a security decision, not syntax trivia. - If you cannot explain your runner isolation model to security, you are not ready for self-hosted.
- Start with GitHub-hosted; add self-hosted only when you hit a concrete constraint (network, hardware, cost at >50k min/month).
Best practices
- Pin third-party actions to full commit SHA, not floating tags.
- Set
timeout-minuteson every job — runaway loops burn budget and block queues. - Use
concurrencygroups to cancel superseded PR runs. - Split untrusted (fork) and trusted (main) workflow paths explicitly.
- Document which events trigger deploy vs verify — onboarding engineers grep workflows first.
Common mistakes
- Using
pull_request_targetto access secrets while running untrusted fork code — classic CVE pattern. - Granting
contents: writeglobally so a workflow can comment on PRs — use a dedicated bot token or minimal scope. - Assuming macOS and Linux runners are interchangeable — path separators, preinstalled tools, and cost differ.
- Storing self-hosted runner registration tokens in CI logs or public repos.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionExplain the GitHub Actions execution model from push to green check.+
Answer
Follow-up
2IntermediateQuestionWhy did GitHub change the default GITHUB_TOKEN to read-only?+
Answer
Follow-up
3IntermediateQuestionWhen would you choose self-hosted runners over GitHub-hosted?+
Answer
Follow-up
4AdvancedQuestionCompare pull_request vs pull_request_target — security implications.+
Answer
Follow-up
5AdvancedQuestionDesign GitHub Actions permissions for a monorepo with 50 teams.+
Answer
Follow-up
Hands-on exercise
Exercise: Audit a sample workflow for security and permissions. Identify three improvements.
# Sample workflow — find the issuesname: Buildon: pull_request_targetpermissions: write-alljobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4with:ref: ${{ github.event.pull_request.head.sha }}- run: npm test- env:AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY }}run: aws s3 sync ./dist s3://prod-bucket
Summary
You understand GitHub Actions as an event-driven execution platform: how triggers become runs, where jobs execute, and how the permissions model protects (or exposes) your org. Next lesson dives into workflow YAML — jobs, matrix, reusable workflows, and environments.