100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
YAML

Jobs, Steps, and Runners

Jobs are independent units of work that run on isolated runners, steps are the ordered commands within a job, and runners are the actual machines that execute them.

GitHub ActionsBeginner8 min readJul 8, 2026
Analogies

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.

yaml
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 staging

Runners: 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

Was this page helpful?

Topics covered

#YAML#CICDToolsPipelinesStudyNotes#DevOps#JobsStepsAndRunners#Jobs#Steps#Runners#Run#StudyNotes#SkillVeris