Build → Test → Deploy¶
Everything in this course lands here: a build job produces something, a test job checks it, and a deploy job ships it — each stage only running once the one before it succeeded. This is the shape almost every real CI/CD pipeline reduces to, however many extra stages get bolted onto the sides.
TL;DR — in 30 seconds:
- A
needs:chain is what makes each stage wait for the one before it — remove it andbuild,test, anddeployall start at once. - Jobs don't share a filesystem:
build's output must be explicitly uploaded as an artifact and downloaded by whatever job needs it, or — for a container image — pushed to and pulled from a registry. - Promoting through
stagingthenproductionis two separate deploy jobs, each declaring its ownenvironment:, so the promotion is an explicit gated step rather than an assumption.
1. The pipeline as a job graph¶
flowchart LR
B["build<br/>produce the artifact"] --> Te["test<br/>needs: build"] --> D["deploy<br/>needs: test<br/>environment: production"]
Each arrow is a needs: dependency (see Jobs, steps & runners). Skip
needs: and all three jobs would start at once — deploy could run before test even finished, which
defeats the entire point of having a test stage.
2. Why an artifact, not just a shared disk¶
Each job in the graph above starts on its own fresh runner with an empty filesystem — jobs don't share
disk the way steps inside one job do. So if build produces a compiled binary or a packaged file that
test or deploy needs, that output has to be explicitly uploaded as an artifact by the build job and
downloaded by the job that needs it.
| Step | Job | What happens |
|---|---|---|
| Produce the output | build |
Compile/package, then upload it as an artifact |
| Consume the output | test |
Download the artifact, run the test suite against it |
| Consume the output | deploy |
Download the artifact, ship it to the target |
Forgetting this is the single most common first-timer mistake: assuming deploy can just "see" whatever
build left on disk, because that's how steps behave within one job.
3. Where a container image fits¶
When the build step's output is a container image (the common case, following on from the containers module), the "artifact" isn't a file passed between jobs — it's an image pushed to a registry by the build job, then pulled from that same registry by the deploy job. Same principle (produce once, consume by reference later), different mechanism than a generic file artifact.
4. Environments as promotion gates¶
A pipeline commonly deploys to more than one target — say staging then production — as two separate
jobs (or two separate workflow runs), each declaring a different environment:. Because each environment
can carry its own protection rules and its own scoped secrets (see
Secrets & environments), "promote to production" becomes an explicit,
gated step in the job graph rather than an implicit trust that testing on staging was enough.
flowchart LR
S["deploy: staging<br/>environment: staging"] --> P["deploy: production<br/>environment: production<br/>required reviewers"]
5. How this connects¶
- The
needs:mechanism that makes this whole shape possible is introduced in Jobs, steps & runners. - The environment gating in §4 is the same mechanism covered in Secrets & environments — this page is where that mechanism gets used for its most common real purpose.
- The Containers · Podman module covers building the image this pipeline's
buildjob would typically produce.
Common gotchas
- Assuming
deploycan just "see" whateverbuildleft on disk. Fix: each job starts on its own fresh runner with an empty filesystem —build's output must be explicitly uploaded as an artifact and downloaded by the job that needs it (§2). - Treating a container image the same as a generic file artifact. Fix: an image is pushed to a
registry by
buildand pulled from that same registry bydeploy— not uploaded/downloaded as an artifact file, even though the underlying principle (produce once, consume by reference) is the same (§3). - Skipping
needs:and assuming the jobs still run in the order they're written. Fix: withoutneeds:, all three jobs start in parallel —deploycould finish beforetesteven reports a result (§1). - Treating "it passed on staging" as sufficient trust for production. Fix: give
productionits own deploy job with its ownenvironment:, protection rules, and scoped secrets (§4) — don't let a passing staging run stand in for an explicit, gated promotion.
Check yourself — why can't the deploy job just read the file build produced from disk?
Because each job in the graph starts on its own fresh runner with an empty filesystem — jobs don't share
disk the way steps inside one job do. build's output has to be explicitly uploaded as an artifact and
then downloaded by whichever job needs it.
Check yourself — how does a container image move from build to deploy differently than a compiled binary artifact does?
It doesn't travel as an uploaded/downloaded artifact file at all — build pushes the image to a
registry, and deploy pulls it from that same registry. Same "produce once, consume by reference later"
principle as a file artifact, just a different mechanism.
You can defend this when you can explain why deploy needing test needing build isn't optional
scaffolding but the actual thing preventing an untested change from shipping, and why an artifact — not a
shared disk — is what carries data between jobs.