The term "CI/CD" comes up quite often in development.
You might run tests with GitHub Actions, or automatically deploy to production when you merge into the main branch.
Many people call this kind of setup "CI/CD" as a whole.
But when you stop to think about it,
- What exactly is CI?
- What exactly is CD?
- What is the difference between Continuous Delivery and Continuous Deployment?
These are surprisingly vague.
I originally thought of it as roughly
CI = automated testing CD = automated deployment
That isn't far off, but it differs a bit from the original meaning.
To understand CI/CD, rather than just looking at definitions of the terms, it's easier to start from
"What problem was it created to solve in the first place?"
What Was the Problem in the First Place?
In software development in the past, it wasn't unusual for each developer or team to work for a fairly long stretch and then integrate their changes at the very end.
Here's the picture.
Developer A ─── Long development ───┐
│
Developer B ─── Long development ───┼── Integration
│
Developer C ─── Long development ───┘
│
▼
Massive conflicts
Test failures
Spec inconsistencies
Even if everything worked in each person's own environment, problems appear the moment everything is put together at the end.
Developer A's changed code was also changed by Developer B, or a change to one feature broke another.
On top of that, weeks or months of changes are integrated all at once, so identifying the cause when something goes wrong is hard.
This is so-called "Integration Hell."
The idea that emerged in response was Continuous Integration.
Continuous Integration
CI stands for Continuous Integration.
The idea itself is quite simple.
If integrating everything at the end is what makes it hard, then integrate more frequently.
That's all.
For example, instead of
Months of development
↓
Months of development
↓
Months of development
↓
Integrate at the end
↓
💥
you repeat a cycle like
Small change
↓
Integrate
↓
Verify
Small change
↓
Integrate
↓
Verify
Small change
↓
Integrate
↓
Verify
If you integrate while changes are still small, it's easier to pinpoint the scope of impact when something goes wrong.
Continuous Integration became widely known in the 1990s as a practice of Extreme Programming (XP).
As Kent Beck systematized XP, he positioned frequent integration of changes by developers as an important practice.
There's one thing worth pinning down here.
CI is not, at its core, a CI tool.
Running GitHub Actions Is Not the Same as CI
Today, when people hear CI, many picture something like this:
git push
↓
GitHub Actions
↓
Lint
↓
Test
↓
Build
Of course, these are important mechanisms for achieving CI.
But the real purpose of CI is
to integrate code changes frequently and continuously verify that those changes haven't broken the system.
GitHub Actions, Jenkins, GitLab CI, and so on are tools that support it.
In other words, the relationship is
Continuous Integration
│
│ used to realize it
▼
GitHub Actions
Jenkins
GitLab CI
CircleCI
etc...
Adopting GitHub Actions doesn't necessarily mean you're practicing CI.
For example, suppose you've been developing on a huge feature branch for six months and finally open a Pull Request.
Even if GitHub Actions runs tests on that Pull Request,
6 months of development
↓
Giant PR
↓
CI run
↓
Merge
most of the problem Continuous Integration was trying to solve remains.
Having automated tests isn't enough. CI includes keeping changes small and integrating continuously.
CI Made Integration Easier
Thanks to CI, the flow
Code
↓
Build
↓
Test
↓
Integrated
became much more efficient.
Automated tests run on every change, so you can immediately check
"Is it safe to integrate this change?"
But here, a different problem appears.
Even when integration worked, getting it to production was hard.
The Next Problem Was Release
Just because code integrates successfully doesn't mean it reaches users as is.
Actually delivering to users can require steps like
Development complete
↓
QA
↓
Write release procedure
↓
Prepare servers
↓
Change configuration
↓
Change DB
↓
Request the operations team
↓
Production release
So even if CI sped up the path
Developer
│
▼
Integrated Software
the last part still remains:
Integrated Software
│
│ this is the hard part
▼
Production
You can integrate every day.
But if you release to production once every three months, the speed at which changes reach users doesn't improve much.
That's where Continuous Delivery came in.
Continuous Delivery
Continuous Delivery is, in Japanese, called "継続的デリバリー."
Now that CI made it possible to integrate code continuously, the next thought was:
Let's keep that code in a state where it can be released to production at any time.
For example, a flow like
Commit
↓
Build
↓
Unit Test
↓
Integration Test
↓
Acceptance Test
↓
Staging
↓
Production Ready
In addition to testing and building, the whole process up to making the software releasable to production is automated.
This flow is called a Deployment Pipeline.
The idea of Continuous Delivery developed in the 2000s at places like ThoughtWorks, and became widely known through the book "Continuous Delivery," published in 2010 by Jez Humble and David Farley.
If CI is about
always keeping things in an integrable state,
then Continuous Delivery is about
always keeping things in a releasable state.
Continuous Delivery Doesn't Necessarily Mean Automatic All the Way to Production
This is a slightly confusing point.
In Continuous Delivery,
the release to production itself may be manual.
For example:
Code
↓
Test
↓
Build
↓
Staging
↓
Production Ready
↓
Manual Approval
↓
Production
Testing, building, and the processing to bring the software to a releasable state are all automated.
However, only the final decision of
whether to release to production right now
is made by a human.
In GitHub Actions, a setup where you configure Approval on the Production Environment and deployment happens once someone approves
Deploy Production
is close to this.
What's required here is
that a state is always maintained where pressing the button lets you release safely.
It's not the case that "a single manual step means it isn't CD."
So What Is Continuous Deployment?
This is why the term CD is confusing.
CD has two meanings:
- Continuous Delivery
- Continuous Deployment
Continuous Deployment is a step further beyond Continuous Delivery.
It fully automates everything through
Code
↓
Test
↓
Build
↓
Deploy
↓
Production
For example, a setup like
Pull Request
↓
CI
↓
Merge to main
↓
Build
↓
Production Deploy
There is no human release approval.
Changes that pass the tests and other checks are reflected in production as is.
This is Continuous Deployment.
The Difference Between Delivery and Deployment
Summing up so far, the difference is quite simple.
Continuous Delivery
Code
↓
Test
↓
Build
↓
Release Ready
↓
Human decision
↓
Production
Keep things in a state where they can go to production at any time.
A human decides when to finally release.
Continuous Deployment
Code
↓
Test
↓
Build
↓
Production
If there are no problems, release to production automatically.
Even the release decision is automated.
So the big difference is
whether a human decision sits right before the production release.
Tracing How CI/CD Came About
Looking at it historically makes things much clearer.
First,
Development
↓
Development
↓
Development
↓
Integrate at the end
↓
Integration Hell
This was the original problem.
So Continuous Integration spread:
Small changes
↓
Frequent integration
↓
Automated verification
Then the next problem became visible:
Code
↓
CI
↓
Integrated
↓
↓
Release is hard
↓
Production
So with Continuous Delivery, the following becomes something you do continuously:
Code
↓
Test
↓
Build
↓
Staging
↓
Release Ready
And if you can build a pipeline that stable, then let's automate everything through
Code
↓
Test
↓
Build
↓
Production
That's Continuous Deployment.
Simplifying quite a bit, the flow is:
Integration Hell
↓
Continuous Integration
↓
Integration got easier
↓
But releasing is hard
↓
Continuous Delivery
↓
Releasing became stable too
↓
Continuous Deployment
CI/CD wasn't a single technology invented all at once.
It's easier to understand as the result of gradually eliminating bottlenecks between changing software and delivering it to users.
The Boundary Between CI and CD
Applied to a modern development flow, it looks something like this:
Developer
│
▼
Pull Request
│
▼
┌─────────────────────┐
│ Continuous │
│ Integration │
│ │
│ Lint │
│ Type Check │
│ Unit Test │
│ Integration Test │
│ Build │
└──────────┬──────────┘
│
▼
Merge
│
▼
┌─────────────────────┐
│ Continuous Delivery │
│ │
│ Artifact Build │
│ Staging Deploy │
│ E2E Test │
│ Release Ready │
└──────────┬──────────┘
│
▼
Production Deploy
If a manual approval comes right before Production Deploy, it's Continuous Delivery.
If it's automated all the way through, it's Continuous Deployment.
Of course, in real systems the boundaries aren't always this clean.
But it becomes easier to sort out if you look at
which parts of the flow from writing code to reaching users are being run continuously.
"We Have CI/CD" Doesn't Tell You Much
In real-world settings, you often hear
We have CI/CD in place.
But that statement alone tells you almost nothing about what's actually being done.
For example, it might be just
Pull Request
↓
Test
Or it might be
Pull Request
↓
Test
↓
Merge
↓
Build
↓
Staging
↓
Manual Approval
↓
Production
Or it might be fully automated all the way:
Pull Request
↓
Test
↓
Merge
↓
Production
All of these can be described with the words "CI/CD."
So in practice, it's easier to understand a project's development flow by looking at things like:
- When does CI run?
- What is being tested?
- When are artifacts built?
- Is deployment to Staging automatic?
- Is deployment to Production automatic?
- Is there an Approval before Production?
The Goal of CI/CD Is Not "Automation"
When you look into CI/CD, many explanations say
"automate your tests"
"automate your deployments"
Of course, automation is important.
But automation itself is not the goal.
What CI wanted to solve was
reducing the risk of integrating changes.
What Continuous Delivery wanted to solve was
reducing the risk of releasing software.
For example, if you release three months of changes at once,
there are a huge number of candidate causes when something goes wrong.
On the other hand, if you frequently repeat
Small change
↓
Test
↓
Deploy
the changes you need to check when a problem occurs are also small.
In other words, I think of CI/CD as a mechanism for
creating a state where you can safely repeat small changes, rather than occasionally making large ones.
Summary
The term CI/CD is often used as a single unit, but each part actually solves a different problem.
Continuous Integration
Integrate changes frequently and find problems early.
Code
↓
Build
↓
Test
↓
Integration
Continuous Delivery
Keep the integrated software in a state where it can be released to production at any time.
Code
↓
Test
↓
Build
↓
Release Ready
↓
Manual Release
Continuous Deployment
Automatically deliver problem-free changes all the way to production.
Code
↓
Test
↓
Build
↓
Production
Seen historically, it developed like this:
Integration is hard
↓
CI
↓
Release is hard
↓
Continuous Delivery
↓
Automate the release decision too
↓
Continuous Deployment
So understanding CI/CD merely as
something that automates testing and deployment with GitHub Actions
is a bit of a waste.
Behind it lies the idea of
keeping changes as small as possible, integrating frequently, and delivering to users safely.
When you look at a CI/CD setup, rather than asking "which tools are being used," looking at
what the flow is from writing code to reaching users
makes the original purpose much easier to see.