Prefer to click through the interactive model?

CI/CD — study guide

The same fragments the interactive model serves, read in order. One source, two views.

From commit to running, automatically

CI/CD is the automated path from a code change to running software. A developer pushes a commit, and machinery picks it up, builds it, tests it, and — depending on how far the automation is trusted — carries it toward production, with nobody hand-carrying the change from one step to the next. The point is to make shipping a code change routine rather than an event.

The name is two practices bolted together. Continuous integration, the CI, is the front of the path: merging every change into the shared main branch frequently and building and testing it automatically, so problems surface within minutes of the change that caused them. Continuous delivery or deployment, the CD, is the far end: keeping that tested result always ready to release, and either releasing it on a person's say-so or shipping every passing change automatically.

The rest of this concept walks that path in order: how continuous integration keeps the main branch healthy, what a pipeline is and how its stages chain together, what the build and test stages actually do, the difference between being ready to ship and actually shipping, why the same built artifact should travel through every environment unchanged, and why a system holding the keys to production is worth guarding.

Merge small, merge often, test every time

Continuous integration is a plain discipline with a demanding name: everyone merges their work into the shared main branch frequently — at least once a day — and every merge is built and tested automatically. Instead of hoarding a big change on a private branch for weeks, you fold small changes into the line everyone shares while they are still small.

The reason to bother is timing. When integration happens constantly and every change runs the test suite the moment it lands, a break announces itself within minutes, while the change is small and its author still remembers what they did. Let integration wait until the end and the breaks pile up into one tangled knot, each change tripping over the last, with nobody sure which one to blame.

This leans on two habits from around it: proposing changes into the shared main branch through pull requests, and having an automated test suite worth running. Continuous integration is what happens when you run that suite on every merge instead of hoping someone remembers to run it later. The main branch stays something you can trust, because it was proven working minutes ago and not months ago.

An ordered chain of stages

a commit triggers stages that run in order; a failing stage halts the line commit build test fails package never runs deploy never runs triggers pass halts each stage must pass before the next begins; the failure stops everything downstream
A commit triggers pipeline stages that run in order - build, test, package, deploy - each passing to the next; when the test stage fails the pipeline halts, so the package and deploy stages never run.

A pipeline is an ordered set of stages that runs automatically when a change arrives — a commit pushed, or a pull request opened. The stages run in sequence, and each one must pass before the next begins like an assembly line - the work moves through stations in a fixed order, and if one station rejects the piece it never reaches the next. When a stage fails, the line stops: the later stages never run, and the pipeline ends early with the failure on record.

The ordering is deliberate, not incidental. Early stages are fast and cheap and catch the common, obvious failures; later stages cost more time and run only once the cheap checks have earned them the right to. Each stage a change survives is a little more confidence that it is safe to ship, bought at the price of a little more time.

That is why a failure early in the line is good news, not bad. The pipeline caught the problem at the cheapest possible moment, before the expensive stages ran and long before the change reached anywhere that matters. A halted pipeline is the system doing its job.

Build it, then prove it

The first stages of a pipeline earn their keep by turning source code into something runnable and then trying to break it. The build stage compiles the code and packages it into an artifact — often a container image, produced with podman build — a single bundle that the later stages and every environment will use. Nothing runs the raw source directly; the built artifact is the thing that moves forward.

The test stage then runs the automated test suite against that freshly built artifact. This is the same suite continuous integration relies on, now aimed at the exact bundle that could end up in production rather than at a developer's working copy. Passing here means the thing that was built, not just the code that was written, behaves as it should.

A failing stage turns the pipeline red and halts it. A build that will not compile never reaches the tests; a test that fails never reaches deployment. The red pipeline is a wall between a broken change and everything downstream, and it costs a few minutes of a machine's time to hold that wall.

Always ready, versus always shipped

tests pass continuous delivery: stop at a human gate human approval gate production production continuous deployment: no gate, straight to production passes a person releases every passing change, automatically
After tests pass, continuous delivery routes the change to a human approval gate before it reaches production, while continuous deployment sends every passing change straight to production with no gate.

These two words get used interchangeably, and they should not be. Continuous delivery means the main branch is kept in a state where it could be released to production at any moment — every passing change is ready to go — but the decision to actually push it there stays with a person like keeping a package boxed and ready to send at any moment, with someone deciding when to actually hand it to the courier. The pipeline runs right up to a manual approval gate and waits, and a human chooses when to open it.

Continuous deployment removes that gate. Every change that passes the pipeline is deployed to production automatically, with no human in the loop, so a merge that goes green this minute is live the next. The pipeline does not pause to ask; passing the tests is the approval.

The whole difference is that one gate. Delivery keeps a hand on the release, which is the right call when a person needs to time the launch or a regulator needs a sign-off. Deployment trades that hand away for speed, and it only works when the tests are trusted enough to be the final word — because after them, nothing else stands between a passing change and real users.

Build once, promote everywhere

build once, then promote that same artifact podman build artifact version 1 staging production produces promote promote the same bytes move forward, unchanged or rebuild per environment - different bytes each time podman build staging (built here) build A podman build production (built here) build B A and B differ: staging did not test what production runs
Building the artifact once and promoting that same artifact through staging into production, contrasted with rebuilding separately for each environment so staging and production run different bytes.

A pipeline should build its artifact exactly once and then move that same artifact through every environment like stamping one part from a mould and moving that exact part down the line, rather than casting a fresh one at every stop. The build stage produces the bundle — say a container image from podman build — and each later step deploys that identical bundle: first to a staging environment that mirrors production, then, unchanged, to production itself. Promotion moves the same bytes forward; nothing is recompiled along the way.

Rebuild for each environment and you have quietly created different things. A build for staging and a separate build for production can pull a slightly newer dependency, land on a different machine, or differ in a hundred invisible ways, which means staging tested what staging built, not what production will actually run. The whole point of a staging environment evaporates if the thing it proves is not the thing that ships.

Building once closes that gap. The artifact carries an immutable version, the bytes never change as it advances, and the version that passed every stage in staging is byte-for-byte the version that lands in production. What you tested is what you ship.

The pipeline is an attack surface

A pipeline holds the keys to production. It can build code and ship it straight to where real users meet it, which makes it a tempting thing to attack. Compromise a single step in the pipeline and you can inject malicious behavior into every build it produces, without ever touching the source code that people actually review — the build machinery becomes the implant, and the source looks clean.

So the pipeline itself has to be defended, not just the code that flows through it. Pin and verify the dependencies it pulls in, so a swapped-out version cannot slip through under a name you trust. Sign the artifacts it produces, so whoever runs them can confirm the bytes came from the real build and were not tampered with on the way. And give the runners that execute each step the least privilege they need, so a step that is compromised cannot reach past its own narrow job.

None of this is exotic; it is the same least-privilege, verify-what-you-trust caution applied to the machinery instead of the product. The pipeline that automates your path to production is also the shortest path an attacker has to it.

Push, and it ships

CI/CD is what turns "I pushed my change" into "my change is live" without a checklist of manual steps waiting to be forgotten. For a personal site or an agent pipeline, wiring the repository to a pipeline makes merging to the main branch the deploy: the build runs, the tests run, and the result goes out, the same way every time, whether it is the first release of the week or the tenth of the day.

The win is repeatability. A person doing deploy steps by hand does them a little differently each time and eventually skips one, and the skipped step is the outage. A pipeline does the identical sequence on every change, so the path to production is boring — and boring is exactly what you want from the machinery that decides whether your users see working software.

That is the payoff of everything here: integrate constantly so breakage surfaces early, chain the stages so nothing ships untested, build the artifact once so what you tested is what runs, and guard the machinery because it holds the keys. Push, and it ships — because the path was built to be trusted.