A pipeline is an automated sequence that takes a code change from commit to (potentially) production. It is composed of stages (logical phases like build, test, deploy), which contain jobs (units of work that can run in parallel), which contain steps (individual commands), executed on runners (the machines or containers that do the work).
Understanding this hierarchy is foundational because every CI system (GitHub Actions, GitLab CI, Jenkins) uses the same shape with different names. A stage groups related work and usually gates the next: tests only run after the build succeeds. Jobs within a stage typically run in parallel on separate runners, which is how you fan out browser or shard combinations. Steps run sequentially within a job and share its filesystem and workspace. Runners are ephemeral by default — each job starts on a clean machine/container, so anything one job needs from another (a built artifact, a coverage file) must be explicitly passed via artifacts or caches, not assumed to persist. This ephemerality is the single most common source of confusion for people new to CI.
A pipeline has a build stage (one job compiling the app), a test stage (three parallel jobs: unit, API, E2E, each on its own runner), and a deploy stage. The E2E job can't see the build job's output automatically — the build job uploads the compiled bundle as an artifact, and the E2E job downloads it, because each runs on a fresh, isolated runner.
# GitHub Actions — stages expressed via job dependencies (needs:)
jobs:
build: # stage 1
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with: { name: dist, path: build/ }
test: # stage 2 — runs only after build
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with: { name: dist, path: build/ }
- run: npm ci && npm testScenario: A teammate's E2E job fails with 'build/ not found', even though an earlier build job clearly produced it and 'it works locally'. They insist the CI is broken. What's actually happening and how do you fix it?