CI/CD Automation Tutorial 0/46 lessons ~6 min read Lesson 15

    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…

    Course progress0%
    Focus
    20 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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 for GITHUB_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

    text
    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.

    GitHub Actions Introduction — system view
    GitHub webhook event
    Input
    Workflow YAML match
    Process
    Runner pool (hosted / se
    Output
    Job steps + GITHUB_TOKEN
    Feedback
    Where this topic sits in the delivery path.
    GitHub Actions Introduction — execution flow
    Define triggers & permis
    Plan
    Author workflow YAML in
    Build
    Runner executes isolated
    Verify
    Artifacts + status back
    Ship
    Follow this loop when designing or reviewing pipelines.

    Step-by-step explanation

    1. 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).
    2. Matching workflows enqueue one or more jobs. Each job requests a runner via runs-on (e.g. ubuntu-latest or [self-hosted, gpu]).
    3. 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.
    4. GITHUB_TOKEN is injected automatically. Its scopes come from the workflow-level permissions: block or repo/org defaults. Writes to contents, packages, or deployments require explicit grants.
    5. 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:

    yaml
    name: CI
    on:
    pull_request:
    branches: [main]
    paths: ['src/**', 'package.json']
    push:
    branches: [main]
    permissions:
    contents: read
    pull-requests: read
    concurrency:
    group: ci-${{ github.ref }}
    cancel-in-progress: true
    jobs:
    verify:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
    with:
    node-version: 20
    cache: npm
    - run: npm ci && npm test

    Execution workflow

    1GitHub Actions introduction — adoption workflow
    1 / 4

    Map git events to pipeline intent

    PR = verify; main push = build artifact; tag = release.

    Draw before YAML.

    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: write on 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 (not pull_request_target with 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 production environment 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_request vs pull_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-minutes on every job — runaway loops burn budget and block queues.
      • Use concurrency groups 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_target to access secrets while running untrusted fork code — classic CVE pattern.
      • Granting contents: write globally 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.

      5 questions
      1BeginnerQuestionExplain the GitHub Actions execution model from push to green check.+

      Answer

      Push triggers webhook → matching workflows in .github/workflows → jobs queued → runner allocated per runs-on → steps execute with injected GITHUB_TOKEN and secrets → results posted as check runs. Branch protection can require specific checks.

      Follow-up

      What happens if two workflows match the same event?
      2IntermediateQuestionWhy did GitHub change the default GITHUB_TOKEN to read-only?+

      Answer

      Supply-chain and fork-PR attacks abused write tokens to push malicious commits or exfiltrate secrets. Read-only default forces explicit permissions elevation in workflows that need writes — making dangerous scopes visible in PR review.

      Follow-up

      How do you grant write access safely for release automation?
      3IntermediateQuestionWhen would you choose self-hosted runners over GitHub-hosted?+

      Answer

      When jobs need VPC/private database access, custom hardware (GPU, ARM), air-gapped compliance zones, or cost optimization above ~50k minutes/month with stable workload. Trade-off: you operate patching, scaling, and isolation.

      Follow-up

      How do you isolate untrusted fork PRs on self-hosted runners?
      4AdvancedQuestionCompare pull_request vs pull_request_target — security implications.+

      Answer

      pull_request runs in a merge commit context with read-only fork code — secrets from base repo are not exposed to fork workflows by default. pull_request_target runs in base repo context with base ref checkout — dangerous with secrets if checkout step switches to untrusted fork SHA. Use pull_request for external contributions.

      Follow-up

      How do integration tests on fork PRs access private npm packages?
      5AdvancedQuestionDesign GitHub Actions permissions for a monorepo with 50 teams.+

      Answer

      Org-level reusable workflows with locked permissions; repo CODEOWNERS on .github/; environment protection on production; OIDC per AWS account; self-hosted pool for data-plane tests; branch protection requiring ci/required jobs; no org-wide secrets — team-scoped environments instead.

      Follow-up

      How do you audit permission drift across repos?

      Hands-on exercise

      Exercise: Audit a sample workflow for security and permissions. Identify three improvements.

      yaml
      # Sample workflow — find the issues
      name: Build
      on: pull_request_target
      permissions: write-all
      jobs:
      build:
      runs-on: ubuntu-latest
      steps:
      - uses: actions/checkout@v4
      with:
      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.

      Ready to mark this lesson complete?Track your journey across the entire course.