Lesson 4 of 30
The machine that actually executes your job — hosted vs. self-hosted, and matrix builds: running the same job across multiple versions at once, and when that's worth the extra minutes.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A runner is the actual machine a job's steps execute on. runs-on: ubuntu-latest in this repo's ci.yml isn't a config option floating in the abstract — it's a real, freshly-provisioned Linux VM that GitHub spins up, runs your ten steps on, and then destroys the moment the job finishes. Every job starts from a clean slate; nothing persists between runs unless you deliberately cache or upload it.
Two flavors:
ubuntu-latest, windows-latest, macos-latest). Zero maintenance, but you're limited to what GitHub offers, and macOS/Windows minutes cost several times more than Linux minutes.A matrix build runs the same job definition multiple times in parallel, once per combination of variables you specify — most commonly across Node versions, OS versions, or both.
This repo's actual ci.yml pins one specific target: runs-on: ubuntu-latest with node-version: 24. That's a deliberate choice — the app deploys to one known Node version on one known OS, so testing more combinations would just burn CI minutes without catching anything real. But here's what it would look like if this were a published library that other teams' Node versions and OSes needed to support:
jobs:
verify:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [20, 22, 24]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: pnpm install --frozen-lockfile
- run: pnpm test
strategy.matrix declares two axes — 2 OSes × 3 Node versions — and GitHub Actions runs all 6 combinations as separate, parallel jobs automatically. ${{ matrix.os }} and ${{ matrix.node-version }} are placeholders substituted per-job. The job summary shows six independent pass/fail results: (ubuntu-latest, 20), (ubuntu-latest, 22), ... (windows-latest, 24).
actions/setup-node's cache: pnpm (from the previous lesson) restores a cache keyed to the lockfile even though the underlying VM is brand new every time. "Ephemeral machine" and "zero memory between runs" aren't quite the same claim.A runner being ephemeral and freshly provisioned raises an obvious question: if the machine is destroyed after every job, how does a step reach a real API key or database password without it being baked into this file in plain text? That's secrets and environment variables — how sensitive values get onto a runner without ever appearing in this YAML or in a log line — and it's the next lesson.