Anatomy of a Pipeline
🧬 Anatomy of a Pipeline
Stages
| Stage | What Happens |
|---|---|
| Build | Compile/transpile code, install dependencies |
| Test | Run the automated test suites from Chapter 1 |
| Deploy | Ship the result somewhere |
Not every pipeline has all three, and some split further (a lint stage, a security-scan stage) — but build → test → deploy is the near-universal backbone.
⚡ Triggers
What causes a pipeline to run — and different triggers commonly map to different stage subsets:
| Trigger | Typical Stages Run |
|---|---|
| Pull request | Build + test only — nothing is merged yet, so nothing deploys |
| Push to main | Build + test + deploy — a merge has actually happened |
| Schedule (cron) | Whatever a periodic job needs — e.g. a nightly full test run |
Manual (workflow_dispatch) | Whatever a human explicitly requests — e.g. deploying a specific version on demand |
🧱 Jobs vs Steps
A Job
A unit of work running on its own dedicated runner — jobs can run in parallel with each other unless a dependency says otherwise.
A Step
One individual command or action within a job, running sequentially on the same machine, sharing that job's filesystem.
This distinction matters for both performance (parallel jobs run the whole pipeline faster) and correctness: steps within one job can rely on an earlier step's output being on disk — a different job cannot, unless that data is explicitly passed between them.
A Pipeline as a Dependency Graph
Jobs can depend on other jobs completing first, making a pipeline a directed graph, not a flat list:
A Simple Graph: Build → [Lint, Test in Parallel] → Deploy
- build — runs first, no dependencies
- lint — depends only on
build; runs in parallel withtest - test — depends only on
build; runs in parallel withlint - deploy — depends on both
lintandtestfinishing successfully
One Long Job vs Parallel Jobs
Everything in One Job
Lint, then test, then build artifacts — all crammed into one linear sequence of steps, running one after another with no parallelism at all.
Correctly Split Jobs
Lint and test identified as genuinely independent work, run as separate parallel jobs — the pipeline finishes in whichever one takes longer, not the sum of both.
💻 Coding Challenges
Challenge 1: Choose Stages for Two Triggers
For a pipeline triggered by (a) a pull request and (b) a push to the main branch, list which stages from this chapter's table should run for each, and explain the one key difference.
Goal: Practice mapping triggers to appropriate stage subsets.
Challenge 2: Draw a Dependency Graph
A pipeline has four jobs: build (no dependencies), unit-tests (depends on build), integration-tests (depends on build), and deploy (depends on both test jobs). Describe which jobs can run in parallel and which must wait.
Goal: Practice reasoning about a dependency graph with more than one parallel branch.
Challenge 3: Identify a Jobs-vs-Steps Mistake
A pipeline has a build job that compiles a binary to ./dist/app, and a separate deploy job that tries to copy ./dist/app to a server — but the deploy job fails with "file not found." Explain why, using this chapter's jobs-vs-steps distinction.
Goal: Practice diagnosing the exact mistake this chapter's tip box describes.
It's a very common early mistake to assume a later job can simply read a file an earlier job produced, the way a later step within the same job reliably can. In reality, each job typically runs on a fresh, isolated machine or container with no automatic file sharing between jobs — a "build" job's compiled output is gone the moment that job's runner shuts down, unless it's explicitly passed forward as an "artifact" (uploaded in one job, downloaded in another). Forgetting this produces exactly the confusing experience of a deploy job failing with "file not found," even though the build job clearly reported success moments earlier.
🎯 What's Next
With the universal structure established, the next chapter makes it concrete with a real tool: GitHub Actions Fundamentals — workflow YAML syntax, the actions marketplace, and a first working workflow.