12 · CI with GitHub Actions
'It works on my machine' is where bugs hide. Continuous Integration runs your tests and linter on every push and pull request, on a clean machine — so a broken change is caught at the PR, not in production. GitHub Actions defines this as a YAML workflow.
One idea, one try. At your own pace.
LEARN BY DOINGPut your intuition to work3 steps
12 · CI with GitHub Actions
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.Continuous Integration runs your test suite and linter AUTOMATICALLY on every push and pull request, on a fresh machine. A GitHub Actions workflow (`.github/workflows/ci.yml`) declares WHEN to run (`on:` push/PR), WHERE (`runs-on:` a clean runner), and WHAT (`jobs:` → `steps:`: checkout, set up Python, install deps, run pytest + ruff) — turning 'it works on my machine' into a verified, green-or-red check on every change.
Without this:
Without CI, tests only run when someone remembers to run them locally — so broken code merges silently, and the bug surfaces in production or in a teammate's clone instead of at the pull request.
You have tests for your training and serving code. The problem is when they run: if running them is a manual ritual someone has to remember, they'll be skipped under deadline pressure, and a broken change will sail into main. Continuous Integration (CI) removes the human from the loop: the tests and checks run automatically, on a clean machine, on every push and pull request. A change is verified before it merges, not after it breaks something.
GitHub Actions is GitHub's built-in CI system. You add a YAML file under .github/workflows/ and GitHub runs it for you on its servers. A workflow has a small, consistent anatomy:
on:— the triggers.on: [push, pull_request]means "run this on every push and every PR". This is the heart of CI — the workflow fires by itself when code changes.jobs:— one or more units of work that run (by default) in parallel, each on its own fresh virtual machine.runs-on:— the machine each job uses, e.g.ubuntu-latest. Crucially this is a clean environment — nothing of "your machine" is there, which is exactly what catches "but it works locally" bugs (a missing dependency, an uncommitted file).steps:— the ordered commands inside a job: check out the code, set up Python, install dependencies, runpytest, runruff. Each step is either a reusable action (uses: actions/checkout@v4) or a shell command (run: pytest).
Two refinements make CI fast and thorough:
Dependency caching. Installing dependencies from scratch on every run is slow. A cache step keeps the pip download cache between runs keyed by a hash of requirements.txt, so installs are near-instant until the dependencies actually change. (The setup-python action can do this with cache: pip.)
A test matrix. strategy.matrix runs the same job across several configurations — e.g. Python 3.10, 3.11, 3.12 — in parallel, so you verify your code on every supported version at once instead of hoping it works beyond the one you developed on.
The outcome is a green ✓ or red ✗ on every pull request. A red check blocks the merge: the broken change is caught at the door. GitHub Actions runs on GitHub's servers (not the browser), so the workflow below is read-along — but on / jobs / runs-on / steps is the whole skeleton.
A `.github/workflows/ci.yml`: trigger `on` push/PR, a `test` job `runs-on: ubuntu-latest` across a Python version matrix, with steps to checkout, set up Python (with pip caching), install deps, run ruff, and run pytest.
CI/CD pipeline: each stage must pass before the next runs — a failure halts the deploy.
When does a GitHub Actions CI workflow with `on: [push, pull_request]` run?
- CI runs your tests and linter automatically on every push and pull request, on a clean machine — catching broken changes at the PR instead of in production.
- A GitHub Actions workflow has a simple anatomy: `on:` (triggers), `jobs:` (parallel units), `runs-on:` (a clean runner), and `steps:` (checkout, setup, install, run).
- Dependency caching (`cache: pip`) keeps installs fast across runs, and a `strategy.matrix` runs the job across multiple Python versions in parallel.
Every collaborative ML repo gates merges on a CI workflow: tests, linting, and often data/model checks run on each PR, so a regression is blocked before it reaches main — the same discipline that makes software teams ship safely.
If you remove it: Tests would run only when someone remembers locally, so broken code merges silently and 'works on my machine' bugs surface in a teammate's clone or in production.