Skip to main content
ANVISoftware Solutions
Lesson 10 of 17Intermediate15 min

Continuous Integration and Delivery

By the end of this lesson

Explain what each term means and what it requires of your codebase.

Continuous integration is a practice, not a product. You cannot install it. It means every change is merged into the shared branch frequently — daily at least — and verified automatically each time, so the branch everyone works from is known to build and pass its tests.

A pipeline tool automates the verifying part, which is why the tool and the practice get confused. A team can run a pipeline on a branch that has been diverging for three weeks and be doing no integration at all. A team can also merge ten times a day into a branch whose tests take forty minutes and nobody trusts, and get none of the benefit.

That is the uncomfortable part of this lesson: the practice makes demands of your codebase, and the demands are harder than configuring the tool.

The terms, used precisely, because they are routinely used interchangeably:

Continuous integration (CI)
Merging work into the shared branch often, and verifying each merge automatically with a build and a test run. The output is confidence that the shared branch works.
Continuous delivery (CD)
Every build that passes is in a deployable state, and deploying is a routine, automated action someone chooses to take. The release decision stays with a human.
Continuous deployment
The same, except a passing build actually goes to production without anyone pressing anything. The release decision is made once, in the pipeline definition.
Pipeline
The automated sequence that runs on a change: build, test, package, deploy. Defined as a file in your repository so it is reviewed and versioned like the rest of the code.
Green build
A run where everything passed. On a healthy team, main being red is treated as the most urgent work in the room, because everyone is now blocked from verifying anything.
Feature flag
A configuration switch that hides incomplete work at run time. This is how you merge unfinished code daily without shipping it to users, and it is the mechanism that makes short-lived branches practical.

What continuous integration requires of your codebase. If these are missing, adding a pipeline produces noise rather than confidence.

  • A test suite that is fast. If verification takes forty minutes, people stop waiting for it and start merging on hope. Under ten minutes for the main check is a reasonable target to aim at
  • A test suite that is reliable. A suite that fails one run in five teaches the team to re-run rather than to read the failure, and that habit is very hard to reverse. One flaky test does measurable damage
  • A build that runs from one command on a clean machine. If the build needs a tool someone installed by hand in 2022, the pipeline will be the thing that discovers it
  • A deterministic dependency restore. Locked versions, so the packages in the pipeline are the packages you tested with
  • Changes small enough to merge daily. A three-week branch is not integrated, whatever the tool reports, and merging it is a separate risky event
  • A way to hide incomplete work, usually a feature flag. Without one, merging daily and releasing weekly are in direct conflict and the branch wins

Delivery and deployment differ by exactly one thing: whether a human decides when a passing build reaches users.

 Continuous deliveryContinuous deployment
After a green buildThe artifact is ready and waiting. Deploying is one deliberate actionIt deploys itself, through the environments you configured
Who decides to releaseA person, often coordinating with support, marketing or a regulatorNobody, per release. The decision was made once when the pipeline was written
Also requiresA reliable pipeline and an artifact you trustAll of that, plus automated checks after deploy, a tested rollback, and monitoring good enough to catch a bad release quickly
Typical release sizeWhatever accumulated since the last releaseOne change, which is the main reason it is safer than it sounds
When a release breaks somethingYou know roughly which set of changes to look atYou know exactly which change, because it was the only one
SuitsMost teams, most systems, and anything with an external release calendarTeams with strong automated checks and a genuine appetite for fixing forward
scripts/verify.sh — one command, run by you and by the pipeline
Shell
#!/usr/bin/env bash
# The same checks the pipeline runs, runnable before you push.
set -euo pipefail

dotnet restore --locked-mode
dotnet build --no-restore --configuration Release -warnaserror
dotnet test  --no-build  --configuration Release --filter "Category!=Integration"

echo "Build and fast tests passed."
  • set -euo pipefail stops the script at the first failing command. Without it a failed test can be followed by a successful echo, and the script exits zero — which the pipeline reads as success. This is a real way for broken code to reach a deployable state.
  • One script, used by people and by the pipeline, means there is no separate CI-only knowledge. When the pipeline fails, you reproduce it with the same command rather than reading a log and guessing.
  • --locked-mode fails the restore if the lock file and the project files disagree. That is what stops a dependency quietly resolving to a different version in the pipeline than on your machine.
  • --no-restore and --no-build reuse the previous step's output. Beyond saving time, it guarantees the tests run against the binaries that were built, rather than triggering a second, slightly different build.
  • The filter excludes the slow integration tests from the local loop. The pipeline runs both, which the next lesson covers — the fast subset is for the check you will actually run before every push.
  • Running this locally is a good habit and it is not continuous integration. What makes it CI is the pipeline running the same commands on every change to the shared branch, whether or not anyone remembered.

Getting from occasional manual verification to continuous integration, in the order that works:

  1. Make the build one command on a clean machine

    Check out the repository into an empty folder and run it. Whatever breaks is an undocumented dependency, and it would have broken the pipeline first. Fix these before automating anything.

  2. Get the test suite fast and honest

    Separate tests that touch a database or the network from those that do not. Delete or fix the ones that fail intermittently. A quarantined flaky test is better than a suite nobody believes, as long as quarantine has an owner and a date.

  3. Run it on every change

    On every push to the shared branch and on every pull request. Verifying only after a merge means the shared branch breaks first and you find out second.

  4. Shrink the branches

    Merge at least daily. Where a change is too large to finish in a day, merge it inert behind a flag. This is the step teams skip, and skipping it means the pipeline verifies branches that have little to do with each other.

  5. Treat a red shared branch as the top priority

    While main is red, nobody can tell whether their own change works. Fixing or reverting it comes before new work. A team that tolerates a red main for days has a pipeline and no continuous integration.

  6. Only then automate the deployment

    Continuous delivery sits on top of trustworthy verification. Automating deployment from a suite nobody trusts moves the same uncertainty closer to your users, faster.

Summary

  • Continuous integration is a practice — frequent merges into the shared branch, each one verified automatically — and no tool provides it
  • It demands a fast, reliable suite, a one-command build, locked dependencies, small changes and a way to hide unfinished work
  • Continuous delivery means every passing build is deployable; continuous deployment means it ships without anyone deciding
  • A red shared branch blocks everyone's ability to verify, so fixing it outranks new work
  • The costs are real: test suite maintenance, flag hygiene, and the discipline to keep changes small

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Think about it

Diagnose the practice

A team has a pipeline that builds and tests on every push. Branches typically live two to three weeks. The test suite takes 35 minutes and fails intermittently, so people re-run it. Main is red about one day a week.

Are they doing continuous integration? What would you change first?

Show solution

No. They have automated verification, and the shared branch is not continuously integrated: three-week branches mean the real integration still happens as a large, risky event at merge time.

Change the flakiness first, before the speed and before the branch length. While the suite fails at random, every other improvement is undermined — nobody can tell a real failure from noise, so a red main cannot be treated as urgent and shorter branches produce nothing but more unreliable runs.

Speed comes second, because a 35-minute check is what pushed people towards long branches in the first place. Separating fast tests from slow ones usually gets the common case under ten minutes without deleting coverage.

Branch length comes third, and it largely fixes itself once verification is quick and trustworthy. Asking for daily merges while the suite takes 35 unreliable minutes is asking people to absorb the cost of the problem rather than fixing it.

Try it yourself

Write your own verify script

Write the single command that verifies your current project from a clean checkout. Then delete your local build output, clone the repository into a fresh folder, and run it.

Note everything that failed for a reason unrelated to your code.

Show solution

The failures are the finding. A missing tool, a hand-installed certificate, an environment variable set in your shell profile, a local database with data nobody else has — every one of those is an undocumented prerequisite, and the pipeline would have hit it on day one.

Fix them in the repository rather than in the pipeline. A dependency installed by a pipeline step is invisible to the next person setting up locally, and the two setups then drift apart.

Keeping the script in the repository has a second benefit worth more than the automation: it is the answer to how do I build this, and it cannot go stale, because the pipeline runs it.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

What is the difference between continuous delivery and continuous deployment?
Why does continuous integration depend on a fast and reliable test suite?

Saved in this browser only.