Lesson 2 · CI/CD & GitHub Actions fundamentals
GitHub Actions anatomy
Workflow → job → step → action, and the events that start it all.
Your win: read any workflow file — its trigger, jobs, steps, and actions — and know what a runner is. This is the grammar the other 98 workflows in the repo are written in.
The four-level hierarchy
GitHub Actions is the CI/CD system built into GitHub. Everything is YAML in
.github/workflows/, and it nests four levels deep:1
on: pull_request # the trigger (event) jobs: test: runs-on: ubuntu-latest # the runner steps: - uses: actions/checkout@v4 # a step calling an action - run: go test ./... # a step running a shell command
Events — what starts a workflow
| Event | Fires when |
|---|---|
push | commits land on a branch |
pull_request | a PR is opened/updated (or labeled) |
workflow_dispatch | a human clicks "Run workflow" (manual) |
schedule | a cron timer |
repository_dispatch | an API/other-workflow signal |
workflow_call | another workflow invokes it (reusable) |
You met all the important ones in Lesson 1's chain: push (cuts the tag),
schedule (the 2-hourly cron), repository_dispatch (build → deploy),
and workflow_dispatch (the manual prod deploy).
Jobs, steps, runners — the details that bite
- Jobs run in parallel by default — each on its own fresh runner. Use
needs:to make one job wait for another. - Steps run in order, sharing the one runner and its filesystem. A step is
either a
run:(shell) or auses:(action). - A runner is a machine. GitHub-hosted (ephemeral, managed) or
self-hosted (you operate it, chosen by
runs-on:).
runs-on:: instead of
hard-coding a runner, jobs read a computed label —
runs-on: ${{ fromJson(needs.requirements.outputs.runners)['<job>'] }}. A
runners composite action decides which self-hosted runner size each job
should use (Lesson 7). And a requirements job even decides which jobs run at
all (Lesson 3). So the workflow grammar is standard; the repo layers dynamic routing on
top.
GitHub — Workflows + Events + Workflow syntax
The workflow/job/step model, the full events list, and the YAML syntax reference.
Check yourself (from memory)
Q1. The correct nesting is…
Q2. By default, jobs in a workflow…
needs:
sequences them. Steps within a job share the disk.
Q3. A manual "Run workflow" button is the event…
workflow_dispatch = manual trigger — how this
repo's prod deploys are started.
needs: to sequence) → STEP (run: shell OR uses:
an action) → ACTION (reusable unit). RUNNER = the machine (runs-on:) —
GitHub-hosted or SELF-HOSTED. EVENTS: push, pull_request (incl.
labeled), workflow_dispatch (manual), schedule (cron),
repository_dispatch (workflow→workflow), workflow_call (reusable).
KEY: jobs get FRESH machines (no shared disk — pass files via artifacts/cache/registry); steps
in a job SHARE the disk. Repo twist: runs-on: is computed dynamically, and a
requirements job decides which jobs even run.