Skip to content

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 and build, test, and deploy all 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 staging then production is two separate deploy jobs, each declaring its own environment:, 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 build job would typically produce.

Common gotchas

  • Assuming deploy can just "see" whatever build left 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 build and pulled from that same registry by deploy — 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: without needs:, all three jobs start in parallel — deploy could finish before test even reports a result (§1).
  • Treating "it passed on staging" as sufficient trust for production. Fix: give production its own deploy job with its own environment:, 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.