Lesson 01 of 8 · Modules
The execution model
How GitHub Actions actually runs your code: events trigger workflows, jobs run on runners (in parallel unless linked), steps share a workspace, contexts and expressions carry data, and caches, artifacts, permissions and concurrency control speed and safety.
The moving parts
- A workflow is triggered by events (
push,pull_request,workflow_dispatchfor manual runs,schedulewith cron in UTC, and many more). - A workflow has jobs. Each job runs on a runner chosen by
runs-onlabels: GitHub-hosted (ubuntu-latest) or self-hosted (your machines or Kubernetes, lesson 03). - Jobs run in parallel unless linked with
needs. Each gets a fresh workspace. - A job's steps run in order on the same runner and share files. A step either runs a shell command (
run:) or uses an action (uses:).
A workflow is a recipe card, and the event is someone saying "start cooking!". Each job is a separate cook at their own kitchen counter (runner); they can work at the same time, but they don't share bowls. If the baker needs the frosting from the other cook, it has to be handed over (an artifact or output).
A first workflow
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install -r requirements.txt
- run: pytest -q
build:
needs: test
runs-on: ubuntu-latest
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: meta
run: echo "version=$(git describe --tags --always)" >> "$GITHUB_OUTPUT"
- run: tar czf app.tgz src/
- uses: actions/upload-artifact@v4
with:
name: app
path: app.tgz
(Major-version tags like @v4 keep examples readable. Lesson 06 explains why production workflows pin actions to a full commit SHA.)
Contexts and expressions
${{ … }} expressions read contexts: github (event, ref, sha, actor), env, vars (configuration variables), secrets, matrix, needs, steps, runner, inputs. They also drive conditions:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
Never paste untrusted input into run: with an expression. Pass it via env: instead:
- env:
TITLE: ${{ github.event.pull_request.title }}
run: echo "PR title is $TITLE"
Speed and safety controls
| Control | Use |
|---|---|
Cache (actions/cache, or cache: in setup actions) |
Dependencies keyed by lockfile hash |
| Artifacts | Pass files between jobs; keep reports and binaries |
permissions |
Least-privilege GITHUB_TOKEN per workflow or job |
concurrency |
One deploy at a time; cancel superseded PR runs |
timeout-minutes |
Stop hung jobs (the default is very long) |
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true # for PR builds; use false for deployments
Try it: your first pipeline (a GitHub repo you own)
- Add the workflow above to a small Python (or any language) repo; push and watch both jobs.
- Break a test; confirm
buildis skipped because itneeds: test. - Add a
workflow_dispatchtrigger with an input, and print it safely viaenv:. - Push twice quickly on a PR branch with the concurrency block, and see the first run cancelled.
- Compare run times with and without
cache: pip.
Going deeper: execution details that bite
pull_requestfrom forks runs without secrets and with a read-only token.pull_request_targetruns in the base repo's context with secrets, so never check out and run the PR's code in it.- Scheduled workflows run on the default branch only, and can be delayed at busy times.
- Set default workflow permissions to read-only in the organisation settings, then grant more per workflow.
- Use environments for deployments (lesson 02): protection rules, required reviewers and environment-scoped secrets.
Recap
- Events → workflows → jobs (on runners) → steps; jobs run in parallel unless linked with
needs. - Contexts and expressions carry data; pass untrusted values through
env:, never straight intorun:. - Cache for speed, artifacts/outputs between jobs, permissions for least privilege, concurrency for safe deploys.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.