"CI/CD" gets used as one word, but it's naming two different disciplines that happen to run in the same tool. Understanding exactly what each one automates — and where the boundary between them sits — is what makes the rest of this domain (testing gates, deployment strategies, rollback) make sense as one coherent pipeline instead of a pile of separate tools.
4 min read
Continuous Integration (CI) is about the code — every time someone pushes a change, an automated process builds it and runs the test suite, so integration problems (two people's changes conflicting, a change that breaks an existing test) surface in minutes, not whenever someone happens to notice weeks later. The name comes from the original problem it solved: teams that only merged branches together occasionally ("integrated" rarely) discovered painful, hard-to-untangle conflicts. Integrating continuously — every push — keeps each individual integration small and cheap to fix.
Continuous Delivery / Continuous Deployment (CD) is about the release — once code has passed CI, getting it into production reliably and repeatedly, without a manual, error-prone release ritual. Delivery means every passing change is ready to deploy, with a human still clicking the button. Deployment means every passing change deploys automatically, with no human gate at all. The two get conflated constantly, but the distinction matters: "continuous deployment" is a much bigger trust commitment than "continuous delivery," since it removes the last human checkpoint before production traffic sees a change.
A typical pipeline for a web application runs a sequence of stages, each one a gate the change must pass before moving to the next:
Any stage failing stops the pipeline there — a deploy stage never runs against code that failed its tests, by construction, not by discipline. This is the actual value of automating it: a human could remember to run tests before every deploy, but a pipeline makes skipping that step structurally impossible rather than just discouraged.
Most CI/CD platforms (GitHub Actions, GitLab CI, CircleCI) express this as declarative config rather than a script. A GitHub Actions example:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run buildEvery run: line is a stage; if any exits non-zero, the whole job fails and later steps don't run. on: pull_request means this runs on every PR too, not just pushes to main — catching problems before a change is even merged, which is cheaper to fix than catching them after.
CI runs in a clean, freshly-provisioned environment every time — no leftover files from a previous run, no locally-installed global package a developer forgot they had, no environment variable set months ago and forgotten. This is deliberate: it's the exact same guarantee containers provide (covered in the Docker & Containers domain) applied to the build/test process itself. A test that only passes because of some undocumented local machine state is actually failing — CI is what surfaces that, and "why does it work locally but fail in CI" is almost always the correct question to be asking, not "why is CI wrong."
Each remaining lesson in this domain deepens one stage or property of this same pipeline: what a good automated testing gate actually checks and how to keep it fast, how artifacts get versioned so "what's running in production" is always a specific, traceable answer, how OIDC-based credentials remove long-lived secrets from the deploy step, how feature flags decouple "deployed" from "visible to users," and the rollout/rollback strategies that make the final deploy step itself safe.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What is the practical difference between Continuous Delivery and Continuous Deployment?
2. Why does a pipeline stop entirely when the test stage fails, rather than continuing to the deploy stage?
3. Why does CI intentionally run in a freshly-provisioned environment every single time?
4. In the example pipeline (build → test → lint → package → deploy), what does the 'package' stage guarantee?
CI/CD & Deployment Pipelines