Jobs, Steps, and Runners
Within GitHub Actions (and conceptually similar systems), a workflow is composed of one or more jobs, each job is composed of an ordered sequence of steps, and each job executes on a runner — a virtual or physical machine that provides the operating system and environment the steps run in. Understanding the isolation boundaries between these three concepts is essential to writing pipelines that behave the way you expect, especially around parallelism, shared state, and failure handling.
Cricket analogy: A full cricket tour is like a workflow made of several jobs — each Test match is a job composed of an ordered sequence of steps (toss, innings 1, innings 2), and each match is played on its own specific ground (the runner) with its own pitch, so understanding these boundaries matters for scheduling back-to-back Tests and handling a rain-abandoned match correctly.
Jobs Run in Isolation, by Default in Parallel
Each job in a workflow gets its own fresh runner instance by default, meaning jobs do not share a filesystem, environment variables, or installed dependencies unless you explicitly wire that up (via artifacts, caching, or outputs). Multiple jobs in a workflow run in parallel unless a dependency is declared between them using the 'needs' keyword, which both serializes their execution and gives the dependent job access to the upstream job's declared outputs.
Cricket analogy: Each innings starts with a fresh pitch report by default — the second innings doesn't automatically inherit the first innings' exact wear pattern unless groundstaff explicitly note it; two back-to-back tour matches happen in parallel on different grounds unless selectors declare a dependency, like resting a bowler in match two based on his match-one workload figures.
Steps Run Sequentially Within a Job, Sharing State
Unlike jobs, steps within a single job execute sequentially on the same runner instance, sharing the filesystem and any environment variables exported via GITHUB_ENV. This is why steps like 'checkout code', 'install dependencies', and 'run tests' are grouped into one job — they need to see each other's side effects — while independent concerns like 'run unit tests' and 'run linting' can be split into separate parallel jobs to save wall-clock time, at the cost of needing to check out the code twice.
Cricket analogy: Within a single innings, steps happen sequentially on the same pitch — openers walking out, building a partnership, the middle order continuing — because each step needs the exact same wear the previous step left; but a separate net-practice session for bowlers can happen in parallel elsewhere, at the cost of setting up a totally separate net pitch.
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:unit
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
deploy:
needs: [unit-tests, lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh stagingRunners: Hosted vs. Self-Hosted
GitHub-hosted runners (labels like ubuntu-latest, windows-latest, macos-latest) are ephemeral virtual machines provisioned fresh for every job and destroyed afterward — nothing persists between runs unless explicitly cached or uploaded as an artifact. Self-hosted runners, by contrast, are machines you register and maintain yourself, useful when you need specific hardware (GPUs), network access to internal resources, or to avoid per-minute hosted runner billing at high volume — but they shift patching, scaling, and security hardening responsibility onto your team.
Cricket analogy: A neutral, ICC-provided venue for a World Cup match is set up fresh for that one game and torn down after — nothing carries over unless explicitly recorded in the scorebook; a team's own home ground, by contrast, they maintain themselves year-round, useful for tailored conditions and cost savings, but they bear the burden of groundskeeping themselves.
Think of a workflow as a company org chart: jobs are separate departments that can work in parallel and don't automatically share information, steps are the sequential tasks one department's employee does in order, and the runner is that employee's desk and computer — fresh and empty at the start of each shift unless something is deliberately left in a shared drawer (cache/artifact).
A frequent source of confusion is expecting an environment variable set in one job to be visible in another job — it won't be, because each job runs on an entirely separate runner. Cross-job data sharing requires explicit mechanisms: declared job 'outputs' (for small values) or uploaded/downloaded 'artifacts' (for files).
- A workflow contains jobs; a job contains steps; a job runs on a runner (a VM or self-hosted machine).
- Jobs are isolated and run in parallel by default; use 'needs' to serialize and share outputs between them.
- Steps within one job run sequentially on the same runner and share filesystem and environment state.
- GitHub-hosted runners are ephemeral and fresh per job; self-hosted runners persist and require your own maintenance.
- Splitting work into parallel jobs saves wall-clock time but loses automatic state sharing (must re-checkout code, etc.).
- Cross-job data must be passed explicitly via job outputs or uploaded artifacts, never via ambient environment variables.
Practice what you learned
1. By default, how do multiple jobs in the same GitHub Actions workflow execute relative to each other?
2. Why do steps within the same job share filesystem state, while jobs generally do not?
3. What must be used to pass a value computed in one job to a different, dependent job?
4. What is a defining characteristic of GitHub-hosted runners?
5. What is one reason a team might choose self-hosted runners over GitHub-hosted ones?
Was this page helpful?
You May Also Like
GitHub Actions Workflow Basics
GitHub Actions workflows are YAML files in .github/workflows that define when a pipeline runs, what jobs it contains, and what steps each job executes.
GitHub Actions Triggers and Events
Learn how GitHub Actions workflows start: the `on:` key, common webhook events, scheduled runs, manual dispatch, and how to filter events by branch, path, or type.
Parallelizing Pipeline Jobs
Explore techniques for running pipeline jobs concurrently — matrix builds, test splitting, and fan-out/fan-in patterns — to cut wall-clock time without sacrificing reliability.
Caching and Build Artifacts
Learn how CI/CD pipelines speed up builds with dependency caching and pass data between jobs using artifacts, and where each technique fits.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics