InfrastructureAll levels

CI/CD

Continuous integration is not a server that runs your tests — it is the habit of merging into one shared branch often enough that conflicts stay small, and the automation exists to make that habit safe. Continuous delivery is the second half: one artifact, built once, promoted through environments without being rebuilt. This page covers how to order the stages, why a flaky test destroys the pipeline's authority, what the deployment strategies actually trade, and the four numbers that tell you whether any of it is working.

CI is about
Merge frequency
The artifact
Built once, promoted
A red build
Stops the line

Three terms that are not synonyms

The words get used interchangeably and they name three different commitments. Continuous integration is a practice about people: everyone merges into the shared branch at least daily, so no branch drifts far enough that merging it becomes an event. The automated build exists to make that safe — it is the consequence, not the definition. A team with an elaborate pipeline and week-long feature branches is not doing continuous integration; it is running tests on a server.

Continuous delivery is the state where any commit that passed the pipeline could be released right now, on a decision rather than on a preparation. Continuous deployment goes one step further and removes the decision: every commit that passes goes to production automatically. The gap between the last two is a business choice, not a technical one — plenty of mature teams practise delivery deliberately and never adopt deployment, because a human gate is exactly what their regulator, their customers or their release calendar requires.

A pipeline has two halves. Continuous integration runs on every push, and its stages are ordered by how quickly each can fail, because the pipeline's real job is answering while the author still holds the change in their head: formatting and unit tests first, the build next, then anything needing a database or a browser. Continuous delivery is the second half: the deployable artifact is built once, from a known commit, and promoted through environments unchanged. A pipeline that rebuilds for production deploys something no environment tested — a dependency resolved differently, a base image that moved — so any difference between environments belongs in configuration supplied at run time.

The rule that carries the most weight in that diagram is the one about the artifact. Build the deployable thing once, from a known commit, and promote that same bytes-identical thing through every environment. A pipeline that rebuilds for production has just deployed something no environment ever tested — a dependency resolved differently, a base image that moved, a build flag that differed — and everything staging demonstrated was about a different artifact. If an environment needs different behaviour, that difference belongs in configuration supplied at run time, never in a second build.

Ordering the stages, and keeping them fast

The pipeline has one job that matters more than correctness: telling the author quickly. A developer who gets an answer in three minutes is still holding the change in their head; one who gets it in forty minutes has started something else, and the cost of the failure is now a context switch rather than an edit. So order the stages by how cheap they are, not by how the codebase is organised — formatting and static analysis first, unit tests next, then the build, then everything that needs a database or a browser.

Three techniques do most of the work of keeping that time down. Run independent stages in parallel, since the pipeline is as slow as its longest path rather than its total. Cache what is expensive to reproduce and cheap to verify — the dependency tree keyed by the lockfile hash, the compiler output keyed by the source — and make sure the cache key changes when the input does, because a stale cache is a wrong answer delivered quickly. And on a pull request, run only what the change can affect; run the full suite on the shared branch, where a slower answer is acceptable.

Secrets are the other thing that has to be right from the first pipeline rather than fixed later. Nothing sensitive belongs in the repository, including in the pipeline definition itself; the platform supplies them at run time and masks them in logs. Scope each credential to the job that needs it, keep production credentials out of pull-request runs entirely — a pipeline that runs code from a fork with production access is an open door — and rotate on a schedule rather than after an incident. The pipeline is a machine with access to everything, and it is a target precisely because of that.

Getting it into production

Once the artifact exists, the remaining question is how the running system moves from the old version to the new one. The strategies differ mainly in what they cost and how quickly they can be undone, and the honest way to choose is to ask how bad the worst case is and how quickly you would notice it.

StrategyHow it worksWhat it costs
RollingReplace instances a few at a time, so both versions serve traffic during the change.Nothing extra to run — but your code must tolerate two versions being live at once.
Blue-greenRun the new version alongside the old, then switch all traffic in one step.Double the capacity for the duration, and a switch that is instant in both directions.
CanarySend a small share of traffic to the new version and watch the metrics before widening.Routing you can control and metrics you trust — without both, it is just a slow rollout.
Feature flagDeploy the code switched off, then turn it on for chosen users independently of deployment.A flag to own and eventually delete; stale flags become branching nobody can reason about.

Whichever you use, the deployment is only half the change: the database schema is the other half, and it does not roll back. That is the reason the expand-and-contract sequence exists — add the new column, write to both, backfill, switch reads, and only remove the old one in a later deploy. Each step is compatible with the version before and after it, so a rollback of the code never lands on a schema that cannot serve it. A migration that runs in the same step as the deploy that needs it removes your ability to go back at exactly the moment you most want to.

And a rollback plan is not a plan until somebody has used it. The failure people meet is not that the button is missing but that nobody knows whether it is safe to press, because the last time it was tried was during an incident. Rehearse it: roll back a real deployment on a quiet afternoon, time it, and find out what it does to in-flight requests, to the cache and to the queue. A rollback nobody has performed is a hope with a shortcut key.

How this shows up in real delivery

Four measurements describe delivery performance better than any amount of tooling inventory, and they are worth tracking because they move in opposite directions when a team optimises the wrong thing. How long a change takes from merge to production. How often you deploy. What share of deployments cause a problem. How long it takes to recover from one. A team that deploys rarely usually does so because deploying is frightening, and it is frightening because each release carries months of change — so the two halves feed each other, in both directions.

Keep the pipeline definition in the repository with the code it builds, reviewed in the same pull request. A pipeline configured by clicking in a web interface has no history, no review and no way to reproduce last month's build, and it is the reason nobody can explain why a job started failing. The same argument extends one step further to the environments themselves: whatever creates the infrastructure should be code in a repository too, so a rebuilt environment matches the one it replaced.

One risk that has moved from theoretical to routine: the pipeline runs third-party code with your credentials. A build step referenced by a mutable tag can change under you between one run and the next, and a compromised one exfiltrates every secret the job can see. Pin build actions and base images by digest rather than by tag, review what a dependency update actually changes in the pipeline, and give each job the narrowest permissions it can do its work with. The supply chain is the part of the pipeline nobody reads until it is the incident.

Where it degrades

  • Long-lived feature branches with a pipeline attached, which is automation without integration.
  • Rebuilding the artifact for each environment, so what reaches production was never tested.
  • A retry button used as flake policy, until nobody reads a red result before pressing it.
  • The slowest checks first, so the author has moved on before the pipeline answers.
  • A red shared branch left overnight, which blocks everyone and normalises being blocked.
  • A destructive migration in the same step as the deploy, which removes the option of rolling back.
  • A pipeline configured by clicking, with no history, no review and no way to reproduce a build.
  • Production credentials available to pull-request runs, including ones opened from a fork.
  • Feature flags that outlive their feature, leaving branching nobody can reason about.

When to use it

Use it when

  • From the first week of any project with more than one contributor, when it is a day of work rather than a migration.
  • Wherever a release is currently a scheduled event people prepare for rather than a routine action.
  • When the same manual steps are performed before every deploy, since those steps are the specification.
  • Continuous deployment specifically, once the change failure rate is low and recovery is fast and rehearsed.

Avoid it when

  • Automating a deploy nobody trusts yet — fix the tests and the rollback first, or you automate the incident.
  • A twelve-stage pipeline on a prototype, where the pipeline takes longer to maintain than the code.
  • Continuous deployment where a wrong release has legal or physical consequences that cannot be undone.
  • Adding stages to compensate for a test suite nobody believes, which multiplies the time without the trust.

Found this useful?

Share it with someone who is working on the same problem.