CI vs continuous delivery vs continuous deployment
Published
Picture a team that merges everyone's work once a month. Merge day takes three days. Half the conflicts are in code nobody remembers writing, and the release that follows breaks in ways no single change explains. Now picture a team that merges every few hours and ships to production twenty times a day. The second team has fewer outages, not more, because every change is small enough to understand and to undo.
The three terms describe how far a team has moved towards the second picture:
- Continuous integration (CI): everyone merges into the main branch at least once a day, and every push is built and tested automatically. A red build is fixed first, before new work.
- Continuous delivery: on top of CI, every change that passes the pipeline is ready to release. Going to production is a button anyone on the team can press, at any time.
- Continuous deployment: there is no button. Every change that passes goes to production automatically, usually gradually and behind feature flags.
So: CI keeps the code working. Continuous delivery keeps it releasable. Continuous deployment releases it. "CD" can mean either of the last two, so ask which one people mean.
Your team's CI takes 35 minutes per pull request and people are batching changes into big pull requests to avoid waiting. What would you change first, and how would you know it worked?
Where the ideas came from#
Grady Booch used the phrase continuous integration in 1991, and Extreme Programming made it a daily practice in the late 1990s. Martin Fowler's article on it, first written in 2000, is still the standard reference. Jez Humble and David Farley's 2010 book Continuous Delivery added the second step: treat the release itself as a boring, automated, repeatable process, so it can happen whenever the business wants.
The pipeline, and why speed matters#
A pipeline is the list of jobs that run on every push: install, lint, type check, tests, build, deploy a preview. Its most important number is how long the author waits for an answer. Under about ten minutes, people wait for it. Over twenty, they switch tasks, come back later, and start batching changes to wait less often, which brings back exactly the big risky merges CI was meant to remove.
Try the three changes teams usually make first.
Beyond those three:
- Run only what changed. In a monorepo, Nx and Turborepo work out which packages a change affects and skip the rest, and remote caching reuses results another machine already computed.
- Fail fast. Put lint and type checks where they finish first, so a typo fails in one minute, not twenty.
- Keep the main branch green. A merge queue (built into GitHub since 2023, and in GitLab as merge trains) tests each pull request on top of everything queued before it, so two changes that pass alone but break together never both land.
How teams set it up#
This is a typical GitHub Actions workflow for a TypeScript service. Jobs in the matrix run in parallel, npm downloads are cached, and the browser tests are split across four machines.
name: ci
on: [pull_request, merge_group] # merge_group runs it again inside the merge queue
jobs:
checks:
runs-on: ubuntu-latest
strategy:
matrix:
task: [lint, typecheck, test:unit, test:integration] # four jobs at once
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 24, cache: npm }
- run: npm ci
- run: npm run ${{ matrix.task }} # integration tests start Postgres with Testcontainers
e2e:
runs-on: ubuntu-latest
strategy:
matrix: { shard: [1, 2, 3, 4] }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 24, cache: npm }
- run: npm ci && npx playwright install --with-deps
- run: npx playwright test --shard=${{ matrix.shard }}/4Around the workflow, four settings do as much work as the YAML:
- Branch rules on main: nobody pushes directly, every change needs a pull request, a review and green required checks.
- A preview deployment per pull request (Vercel, Netlify and most platforms do this), so a reviewer in another time zone can click the change instead of imagining it.
- Dependency updates as small automatic pull requests from Dependabot or Renovate, which CI then tests like any other change.
- No long lived cloud keys in CI. GitHub Actions, GitLab and others can log in to AWS, GCP and Azure with short lived OIDC tokens, so there is no secret to leak.
In March 2025 the popular tj-actions/changed-files action was compromised: its version tags
were moved to a malicious commit that printed CI secrets into build logs, in thousands of
repositories that used it. A tag like @v4 can be moved; a full commit SHA cannot. Pin third
party actions to a SHA (Renovate and Dependabot can keep the pins up to date), and give each
workflow only the permissions it needs.
- GitHub ActionsCI built into GitHub. The default for most new projects
- GitLabGitLab CI/CD, with merge trains
- CircleCIHosted CI with strong caching and parallelism
- BuildkiteRuns on your own machines, popular at large scale
- ArgoArgo CD: GitOps deploys to Kubernetes from a repo
- VercelPreview deployment for every pull request
- RenovateAutomatic dependency update pull requests. Dependabot is GitHub's own
- CodecovCoverage reports on each pull request. SonarQube adds static analysis
Measuring it: the DORA metrics#
The DORA research program, now run by Google Cloud, has surveyed tens of thousands of engineers since 2014. It found that four numbers separate strong delivery teams from weak ones, and that speed and stability go together rather than against each other:
- Deployment frequency: how often you ship to production.
- Lead time for changes: from commit to running in production.
- Change failure rate: how many deploys cause a problem that needs fixing.
- Recovery time: how long it takes to restore service when one does.
The first two are speed, the last two are stability. Interviewers like this list because it shows you think about delivery as something to measure, not a matter of opinion.
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| Continuous integration | Small merges, conflicts found within hours, and a main branch that always builds. | Needs a fast, trusted test suite and the habit of fixing red builds first. | Every team, always. It is the floor, not an advanced practice. |
| Continuous delivery | Releasing is boring and possible at any moment, so fixes reach users in minutes. | Needs automated deploys, safe database migrations and rollbacks that work. | Most web services and APIs. A human still chooses when to release. |
| Continuous deployment | The shortest lead time: merged code is live within the hour, with no release queue. | Needs feature flags, canary releases, good monitoring and automatic rollback. | Mature teams with strong tests and observability. Less common for mobile apps, which go through app store review. |
In the interview#
Checkpoint#
1. What separates continuous delivery from continuous deployment?
2. Two pull requests each pass CI alone, but break main when both merge. What prevents this?
3. Which pair of DORA metrics measures stability?
- CI keeps it working, continuous delivery keeps it releasable, continuous deployment releases it.
- Under 10 minutes, people wait for CI. Over 20, they batch changes, which brings back big risky merges.
- Cache, parallelise, shard. Then fix whichever bar is now the longest.
- Merge queues keep main green. Pin third party actions to a commit SHA.
- DORA: deployment frequency, lead time, change failure rate, recovery time.
Continuous integration means everyone merges to main at least daily and every push is built and tested automatically, with a red build fixed first. Continuous delivery adds that every passing change is releasable at the press of a button; continuous deployment removes the button, so passing changes ship automatically, usually as a canary and behind feature flags. The pipeline's key number is how long the author waits: past about twenty minutes people batch their changes, so I cache dependencies, run jobs in parallel, shard browser tests and fix the longest job first. A merge queue tests each change on top of the ones ahead of it so main stays green, and I pin third party actions to commit SHAs and use OIDC instead of stored cloud keys. I measure delivery with the four DORA metrics: deployment frequency, lead time, change failure rate and recovery time.