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.