Integrate constantly, and check every time
Continuous integration means every push is built and tested automatically, so a broken change is found within minutes, by the person who just made it.
Through a pipeline that turns a commit into one artifact, tests it, and then promotes those exact bytes from environment to environment. Every stage is a gate that can stop the line.
One branch, one pipeline at a time. Error counts are per 100 requests: they show the size of the damage, not real traffic.
A good change. It's built once, tested, published as an artifact, and that exact artifact goes to staging and then production. Nobody copies files to a server.
Ready. Production runs f1bbcd8. Push a change and follow it.
| # | commit | build | test | publish | staging | approve | prod |
|---|---|---|---|---|---|---|---|
| 1 | f1bbcd8 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Continuous integration means every push is built and tested automatically, so a broken change is found within minutes, by the person who just made it.
The pipeline publishes one immutable artifact per commit. Staging and production pull that same digest. What was tested is exactly what ships.
Continuous delivery keeps every green commit ready to release, with a person pressing the button. Continuous deployment removes the button.
Live: the stage running now
on: push to mainbuild: compile → once per committest: unit + integration → red stops the linepublish: image@sha256:… → immutable artifactstaging: deploy that artifact → smoke testsapprove: a person, or a ruleprod: deploy that artifact → watch the error rateLive: this reads the simulation above
$ deploy history production
ACTION COMMIT ARTIFACT
deploy f1bbcd8 sha256:dbefbb9…Each stage costs more than the one before and catches what the earlier ones can't. A unit test runs in seconds. A smoke test needs a deployed environment. Only real traffic exercises everything at once.
So the order is chosen to fail cheaply: the earlier a problem is caught, the fewer people it touches. A red test inconveniences one developer. A bad release inconveniences everyone.
Look at the history: a rollback is a deploy of an artifact the registry already holds. That's only possible because artifacts are kept and nothing is built on the way to production.
Some failures depend on real data, real load or real users. No pre-production check finds them reliably, so plan for how much damage one can do.
Send a slice of traffic first, compare its error rate with the old release, and stop automatically if it's worse. Blue-green and feature flags limit exposure in other ways.
Flaky tests get re-run until green, then ignored. A pipeline is only as strong as its weakest gate, and a gate everyone bypasses isn't one.
A release with a bug reaches production and you roll back. What does the pipeline do?
The same bug, released as a canary taking 10% of traffic. Out of every 100 requests, how many fail before the release is stopped?
Most teams run the same build and tests on every pull request, before merging, so main stays green. Only the merge to main continues to publishing and deployment. The GitHub Actions topic shows that split.
Rolling back code is easy because artifacts are immutable. Rolling back a database schema isn't. Migrations are usually written to be compatible with both the old and the new release, so the code can be rolled back on its own.
Here the canary fails on a clear signal. Real canaries compare error rates and latency between two groups, need enough traffic to tell the difference, and run for minutes or hours.
One run at a time keeps this readable. Real pipelines overlap, so release stages are usually serialised, and a newer approved release can supersede an older one.