The mental model: an event triggers a workflow, the workflow runs jobs on runners, each job runs steps, and steps run shell commands or reusable actions.
event (push, PR, tag, schedule, manual)
└─ workflow .github/workflows/*.yml
├─ job A → runner VM (ubuntu-latest) ← jobs run in PARALLEL by default
│ ├─ step: uses: actions/checkout@v7 (an action)
│ └─ step: run: npm test (a shell command)
└─ job B needs: A ← ordered with needs
Every job gets a fresh machine. Nothing is shared between jobs unless you pass it (outputs, artifacts, cache, registry).
| Term | Meaning |
|---|---|
| Workflow | A YAML file in .github/workflows/. One repo can have many. |
| Event | What starts it: push, pull_request, workflow_dispatch, schedule, release, workflow_call |
| Job | A group of steps on one runner. Parallel unless needs: |
| Step | One run: command or one uses: action, executed in order |
| Action | Reusable step: owner/repo@version (Marketplace), local ./path, or docker://image |
| Runner | The machine: GitHub-hosted (ubuntu-latest, windows-latest, macos-latest, ubuntu-24.04-arm) or self-hosted |
| Context | Data you read with ${{ }}: github, env, secrets, vars, steps, needs, matrix, runner |
| Secret / Variable | Encrypted value (secrets.X) / plain config (vars.X) at repo, environment or org level |
| Environment | Named target (staging, production) with its own secrets and approval rules |
| Artifact / Cache | Files passed between jobs / reused dependencies between runs |
.github/workflows/ci.yml.github/actions/<name>/action.ymlghcr.io/<owner>/<image> (GitHub Container Registry, under Packages)Public repos: GitHub-hosted runners are free. Private repos: a monthly free minutes quota, then per-minute billing (Linux is cheapest; Windows and macOS cost more). Self-hosted runners use your own machines.