Pipelines & Stages
A pipeline is the assembly line a change rides from "pushed" to "deployable": an ordered series of stages, each a gate the change must pass. The concepts here are identical across every CI system — GitHub Actions, GitLab CI, Jenkins — only the YAML dialect changes.
💡 The Bottom Line
Order stages cheapest-first so failures are fast (fail fast). Build once, producing an artifact; every later stage — including production deploy — uses that exact artifact, never a rebuild. Within a stage, independent jobs run in parallel.
The classic shape
┌───────┐ ┌──────────┐ ┌──────────────┐ ┌─────────┐ ┌────────┐
│ Lint │ → │ Build │ → │ Test │ → │ Staging │ → │ Prod │
│ ~30s │ │ ~2min │ │ (parallel) │ │ deploy │ │ deploy │
└───────┘ └─artifact─┘ │ unit | e2e │ └─────────┘ └────────┘
└──────────────┘
- Fail fast — lint costs 30 seconds; end-to-end tests cost 20 minutes. Cheap gates first means a typo is reported in half a minute, not half an hour, and expensive compute never runs on code that can't pass the basics.
- Artifacts — the build stage's output (a bundle, a JAR, a Docker image) is saved and passed forward. Tests test the artifact; staging runs the artifact; production gets the same artifact, byte for byte.
- Parallel jobs — unit tests and browser tests don't depend on each other, so they run simultaneously. Stages are sequential; jobs within a stage need not be.
Why "build once" is a rule, not a preference
If staging tested build #1 and production runs a freshly-made build #2, you deployed something that was never tested — different dependency resolution that hour, different timestamp, different anything. The artifact is the thing you verified; promotion means moving that thing forward unchanged. This is also why Docker images make such good artifacts: the image tested in staging is bit-identical to the one production pulls (the Docker track's ":latest" warning is this same principle from the other side).
Environments as promotion rungs
Most teams run the artifact up a ladder: an integration environment (always tracking main), then staging (production-like, final rehearsal), then production — often with a manual approval gate before the last rung. Each rung raises confidence; the artifact never changes, only your certainty about it.
🙋 There Are No Dumb Questions
Q: Where do database migrations fit in a pipeline?
A: As a deploy-stage step, run against each environment right before (or as) the new version
starts — and written to be backward compatible, so the old code still running
during the rollout doesn't break (add a column, don't rename it mid-flight). Migrations are
the classic example of why staging should mirror production: it's your rehearsal for exactly
this step.
⚠️ Watch Out
Guard your pipeline's total runtime. Every added check is worth it in isolation, and the sum quietly grows until "push to green" takes 50 minutes and developers batch changes to avoid the wait — which un-does the small-merges habit from Lesson 2. Parallelize, cache dependencies, and treat pipeline speed as a feature with an owner.
🔧 Try It Yourself
Design your project's pipeline on paper — this exercise takes ten minutes and becomes your YAML file almost line-for-line later:
Stage 1 (seconds): lint? typecheck? format check?
Stage 2 (minutes): build → what exactly is my artifact?
Stage 3 (minutes): which test groups can run in parallel?
Stage 4: deploy where first? what proves it healthy?
Gates: which arrow needs a human approval?
If you can't name the artifact, that's finding #1 — a pipeline that rebuilds at every stage has nothing verifiable flowing through it.
✅ Quick Check
Why should production deploy the exact artifact built earlier in the pipeline, rather than rebuilding?
Next up: the pipeline ends at "deploy" — but how you swap live traffic to new code is its own craft: rolling, blue-green, canary, and flags.