If you have made it this far, it is because at some point you have lived through — or been told about — a scene like this one: Friday afternoon, someone builds the project on their laptop, uploads the files to a server and crosses their fingers. This lesson is the starting point for leaving that scene behind. We are going to define precisely what the acronym CI/CD means, distinguish the three practices hiding behind it (because there are three, not two) and settle the vocabulary we will use throughout the course. This is not a lesson about tools or configuration: it is the lesson that makes everything else make sense. If you confuse Continuous Delivery with Continuous Deployment — the industry's most common mistake — the rest of the course will sound like noise. By the end, you will be able to name every piece of the process and you will understand why the team behind our example project, Reservalia, has a serious problem.

Contents

  1. The problem CI/CD sets out to solve
  2. What continuous integration (CI) is
  3. What continuous delivery is
  4. What continuous deployment is
  5. The two "CD"s head to head
  6. The full flow: from commit to production
  7. Core vocabulary for the course
  8. Common mistakes and tips
  9. Exercises
  10. Conclusion

  1. The problem CI/CD sets out to solve

1.1. Where Reservalia starts

Throughout the course we will work on a fictional project called Reservalia, a SaaS appointment booking platform for small businesses: hair salons, dental clinics, car repair shops. The team is small: Marta (tech lead), Diego (backend developer) and Nuria (SRE). We will look at it in detail in lesson 01-04; for now all that matters is how they work today:

  • Diego builds on his laptop. The version of Node he has installed is not exactly the one on the server.
  • He uploads the resulting files over SFTP to a single production server.
  • He runs the database migrations by hand, pasting SQL into a psql console.
  • Tests are run "whenever someone remembers".
  • All of this happens on Friday afternoons and takes about 3 hours.
  • The result: two serious production incidents last quarter.

This process is not bad through carelessness: it is bad by design. Every one of those steps depends on one specific person, at one specific moment, not making a mistake. And people do make mistakes.

1.2. Integration hell

There is a second problem, less visible but just as expensive. Imagine Marta and Diego each work on their own branch for two weeks:

  • Marta rewrites the email notification system.
  • Diego changes the appointment data model to support recurring bookings.

Neither of them touches the other's work, apparently. But the day they try to merge both branches, conflicts appear in 40 files, the signature of a function they both use has changed, and Marta's tests fail because of a schema change Diego made. What should have been an afternoon turns into three days of "sorting out the integration".

That is integration hell: the cost of merging grows non-linearly with the time branches live apart.

graph LR
    subgraph "Late integration: concentrated pain"
        A1[Day 1] --> A2[Day 5] --> A3[Day 10] --> A4["Day 14<br/>MERGE<br/>3 days of conflicts"]
    end
    subgraph "Continuous integration: pain spread out"
        B1["Day 1<br/>merge"] --> B2["Day 2<br/>merge"] --> B3["Day 3<br/>merge"] --> B4["Day 14<br/>merge<br/>10 minutes"]
    end

The key intuition, which will come back again and again in this course: if something hurts, do it more often. Merging hurts because you do it rarely. Deploying hurts because you do it rarely. The counter-intuitive answer from CI/CD is to increase the frequency until each individual operation becomes trivial.

  1. What continuous integration (CI) is

Definition. Continuous integration is the practice whereby every team member integrates their work into the mainline at least once a day, and every integration is verified automatically by a build-and-test process that runs in a clean, shared environment.

There are three ideas inside that definition, and all three matter equally:

  1. Integrate often. Each person's code returns to the main branch within hours, not weeks. If your branch lives for three weeks, you are not doing CI even if you have a CI server configured.
  2. Verify automatically. Merging is not enough: an automated system builds, runs the tests and checks quality. Without automated verification, integrating often just means breaking the main branch more often.
  3. In a clean, shared environment. Not on Diego's laptop. On a machine that starts from scratch every time, so that "it works on my machine" stops being a valid sentence.

2.1. What CI is not

It is worth defusing three very widespread misunderstandings:

Misunderstanding Reality
"We have CI because we have a CI server" If branches live for weeks, you have a build server, not continuous integration.
"CI means running tests" Running tests is part of CI. CI is the practice of integrating; tests are the verification mechanism.
"CI is the tool's job" CI is above all a team agreement: what happens when the build fails, how long a branch may live, who fixes what.

2.2. The implicit CI contract

When a team genuinely adopts CI, it signs an unwritten contract:

  • The main branch is always healthy. If the build is red, fixing it is the team's number one priority, above any feature.
  • Nobody goes home with a broken build. The change is reverted and investigated calmly the next day.
  • Every change is verified before it is integrated, not after.

Without that contract, automation turns into a traffic light everyone ignores. We will come back to this in module 2.

  1. What continuous delivery is

Definition. Continuous delivery is the practice of keeping software always in a deployable state, so that any version that has passed the pipeline can be taken to production at the push of a button, at any moment, with a known level of risk.

Continuous delivery takes continuous integration for granted and adds a layer on top: it is not enough for the code to build and pass the tests; there must be an artifact that is built, versioned, tested in production-like environments and ready to deploy.

The key phrase is "at the push of a button". In continuous delivery:

  • The technical process of deploying is fully automated.
  • The decision to deploy is still human.

That human decision is not a flaw in the system: it is a deliberate choice. There can be perfectly legitimate reasons not to deploy automatically:

  • A marketing campaign that starts on Tuesday, with a feature that must not be visible before then.
  • A regulatory compliance requirement demanding recorded approval.
  • A team that does not yet trust its test suite enough.

What matters is that, when someone decides to deploy, there is no manual work to do: only to authorise it.

  1. What continuous deployment is

Definition. Continuous deployment is the practice whereby every change that passes the automated pipeline is deployed to production automatically, with no human intervention.

It is continuous delivery without the button. The manual gate is removed: if the pipeline is green, the change reaches users. No meetings, no deployment windows, no Friday afternoons.

This sounds reckless and, without solid foundations, it is. Continuous deployment only works if it rests on:

  • A test suite the team genuinely trusts (module 2).
  • Progressive deployment strategies — canary, blue-green — to limit the blast radius (module 3).
  • Feature flags to separate "deploying code" from "enabling a feature" (module 3).
  • Automatic rollback when metrics degrade (module 3).
  • Monitoring that spots the problem before the customer does (module 3).

Note something important: continuous deployment is not a mandatory goal for every team. It is an option. A great many excellent organisations deliberately stop at continuous delivery. What is non-negotiable is the ability to deploy at any moment; whether you use it automatically or not is a decision that depends on context.

  1. The two "CD"s head to head

Here is the core of the lesson. The CI/CD acronym is deliberately ambiguous, and that causes constant confusion in interviews, documentation and team conversations.

Aspect Continuous integration (CI) Continuous delivery Continuous deployment
What it automates Building and testing every integrated change Everything in CI + packaging, versioning and deployment to earlier environments (dev, staging) Everything in delivery + deployment to production
Where the manual gate sits When merging to the main branch (code review) Before production: someone presses "Deploy" There is no manual gate
Who decides to deploy Not applicable: CI does not deploy A person (tech lead, product owner, release manager) The pipeline: if it is green, it ships
Output of the process A verified build An artifact ready for production at any moment A change already in production
Question it answers "Does this change break anything?" "Could we deploy right now?" "Is it deployed yet?"
Prerequisite Version control and automated tests Solid CI + reproducible environments Solid delivery + progressive deployment + monitoring + rollback
Risk if maturity is missing Red builds that everyone ignores Artifacts nobody dares to deploy Production incidents every day

One way of remembering it that works very well:

  • Delivery = "we can deploy whenever we want" → the capability is automated, the decision is human.
  • Deployment = "we deploy whenever the pipeline allows it" → the decision is automated too.

And an inclusion relationship worth committing to memory:

graph TD
    CD2["Continuous Deployment"] --> CD1["Continuous Delivery"]
    CD1 --> CI["Continuous Integration<br/>CI"]
    CI --> VCS["Version control + automated tests"]

    style CD2 fill:#c9e4ff,stroke:#333
    style CD1 fill:#d9f2d9,stroke:#333
    style CI fill:#fff2cc,stroke:#333

Read it from the bottom up: there can be no continuous deployment without continuous delivery, and no continuous delivery without continuous integration. Any attempt to skip a rung produces a fragile system. If Reservalia tried to deploy automatically to production tomorrow without reliable tests, it would not have two incidents per quarter: it would have two per week.

  1. The full flow: from commit to production

Let us look at the journey a change of Diego's will make over the whole course. This diagram is the mental map of the entire course:

flowchart LR
    A["👤 Diego<br/>makes a commit"] --> B["📥 push / pull request<br/>to the repository"]
    B -->|trigger| C{{"Pipeline started"}}
    C --> D["🔨 Stage: Build<br/>compile, install dependencies"]
    D --> E["🧪 Stage: Test<br/>unit, integration, lint"]
    E --> F["📦 Artifact<br/>versioned Docker image"]
    F --> G["🗄️ Artifact registry<br/>ECR"]
    G --> H["🚀 Deployment to dev"]
    H --> I["🚀 Deployment to staging"]
    I --> J{"Manual gate?"}
    J -->|"Yes → Continuous Delivery"| K["👤 Someone approves"]
    J -->|"No → Continuous Deployment"| L
    K --> L["🚀 Deployment to prod"]
    L --> M["📊 Monitoring<br/>and feedback"]
    M -.->|"if something fails"| N["⏪ Rollback"]

Read the diagram by identifying where each practice fits:

  • From A to E (commit → build → test): this is continuous integration. We will build it in module 2.
  • From F to I (artifact → registry → dev → staging): this is continuous delivery. Modules 2 and 3.
  • The step J → L with no human intervention: this is continuous deployment. Module 3.
  • M and N (monitoring and rollback): the safety net that makes all of the above possible. Module 3.

  1. Core vocabulary for the course

Let us now settle the terms. We will use them without defining them again, so this section is worth reading carefully. I have grouped them into families.

7.1. The "version control" family

Term Definition Example in Reservalia
Repository The store holding the source code and its complete history of changes. github.com/reservalia/reservalia
Commit A confirmed unit of change, with an author, a date, a message and a unique identifier (SHA). a3f9c21 — "fix: validate appointment overlap"
Branch A parallel line of development that can later be merged into another. main, feat/recurring-bookings
Pull request / merge request A proposal to merge one branch into another, opening up review and automated verification. Diego's PR #142 towards main

One detail we will use a great deal: the commit SHA is the canonical identity of a change. When we ask ourselves in module 3 "which exact version is in production?", the correct answer will never be "the latest one", but a specific SHA.

# The SHA uniquely identifies the state of the code.
# This command returns the short SHA of the current commit:
git rev-parse --short HEAD
# → a3f9c21

# And this one, the exact commit date in ISO 8601 format.
# We will use it in lesson 01-05 to calculate lead time.
git show -s --format=%cI HEAD
# → 2026-03-14T10:22:41+01:00

7.2. The "pipeline execution" family

Term Definition Important nuance
Pipeline The complete, automated sequence that takes a change from commit to its destination. It is the umbrella concept: it contains stages, which contain jobs, which contain steps.
Trigger The event that starts a pipeline run. A push to a branch, opening a PR, a tag, a schedule (cron), a manual run.
Stage A logical grouping of work inside the pipeline, usually sequential. buildtestdeploy. A stage normally waits for the previous one to finish.
Job An independent unit of execution inside a stage. Several jobs in the same stage can run in parallel. test-api and test-web run at the same time.
Step A specific command or action inside a job, in order. npm ci, then npm test.
Runner / agent The machine (physical, virtual or container) where a job runs. An ephemeral Ubuntu runner that is destroyed when it finishes.

The hierarchy, visually:

graph TD
    P["PIPELINE<br/>started by a trigger"]
    P --> S1["STAGE: build"]
    P --> S2["STAGE: test"]
    P --> S3["STAGE: deploy"]
    S2 --> J1["JOB: test-api<br/>on ubuntu runner"]
    S2 --> J2["JOB: test-web<br/>on ubuntu runner"]
    J1 --> ST1["step: npm ci"]
    J1 --> ST2["step: npm run test"]

There is a distinction about runners that will matter a great deal later on:

  • Ephemeral: created clean for every job and destroyed when it finishes. It guarantees repeatability. It is the default model in GitHub Actions.
  • Persistent: a machine reused across runs. It is faster (it keeps caches) but it accumulates state, and that state is a classic source of builds that "only work the second time". Typical of old Jenkins installations.

7.3. The "output and destination" family

Term Definition Example in Reservalia
Artifact The packaged, versioned product of a build, ready to deploy or distribute. The Docker image reservalia/api:a3f9c21
Artifact registry A versioned store where artifacts are published and retrieved from. Amazon ECR
Environment A deployed, runnable instance of the system, with its own configuration and its own data. dev, staging, prod
Promotion Taking the same already-built artifact from one environment to the next, without rebuilding it. Promoting api:a3f9c21 from staging to prod
Reproducible build The property whereby building the same commit always produces a functionally equivalent artifact. Pinning Node 20.11.0 and using npm ci with package-lock.json

The last two deserve elaboration, because they are the most widely misunderstood.

Promotion: build once, deploy many times. This is one of the golden rules of CI/CD. The artifact tested in staging must be the very same binary that reaches production, byte for byte. If you rebuild for production, you are deploying something nobody has ever tested.

graph LR
    C["commit a3f9c21"] --> B["BUILD<br/>only once"]
    B --> ART["artifact<br/>api:a3f9c21"]
    ART --> D["dev"]
    ART --> S["staging"]
    ART --> P["prod"]

    style ART fill:#d9f2d9,stroke:#333,stroke-width:2px

And what not to do:

graph LR
    C["commit a3f9c21"] --> B1["build for dev"] --> D["dev"]
    C --> B2["build for staging"] --> S["staging"]
    C --> B3["build for prod ⚠️<br/>artifact never tested"] --> P["prod"]

    style B3 fill:#ffd6d6,stroke:#c00,stroke-width:2px

If the artifact is the same in all three environments, how does the configuration change then? From the outside: environment variables and secrets injected at deployment time. The artifact does not know which environment it lives in.

# The SAME artifact, different configuration depending on the environment.
# In staging:
DATABASE_URL="postgres://reservalia@rds-staging:5432/reservalia"
LOG_LEVEL="debug"

# In production:
DATABASE_URL="postgres://reservalia@rds-prod:5432/reservalia"
LOG_LEVEL="info"

Reproducible build. Two builds of the same commit giving the same result is not automatic: you have to earn it. The usual enemies are:

  • Unpinned tool versions (plain node instead of node@20.11.0).
  • Dependencies with no lock file, which resolve to the latest available version.
  • Installing with npm install (which can modify the lock) instead of npm ci (which respects it strictly).
  • Timestamps or absolute paths baked into the artifact.
# ❌ Not reproducible: "^4.18.0" may resolve to 4.18.2 today and 4.19.0 tomorrow
npm install

# ✅ Reproducible: installs EXACTLY what package-lock.json says
# and fails if the lock and package.json are inconsistent
npm ci

This is exactly Diego's problem: he builds on his laptop, with his version of Node and his node_modules accumulated over months. His build is not reproducible, and that is why nobody can rebuild what is in production.

Common Mistakes and Tips

Mistake 1: believing that "CD" always means the same thing. When someone says "we have CI/CD", always ask: "is the production deployment triggered by a person or by the pipeline?". It is the only question that disambiguates. In a job interview, telling delivery apart from deployment and explaining why you would choose one or the other puts you ahead of most candidates.

Mistake 2: thinking CI/CD is a tool you install. GitHub Actions does not give you continuous integration, just as buying a pair of trainers does not make you fit. The tool executes; the practice is held up by the team. A team with three-week branches and a hugely expensive CI server is not doing CI.

Mistake 3: rebuilding the artifact for every environment. If the deploy to production compiles again, what reaches users is not what was validated in staging. Build once, promote the same artifact. We will formalise this in lesson 02-06.

Mistake 4: confusing deployment with release. Deploying is putting the code into production; releasing is enabling the feature for users. With feature flags you can deploy on Monday and enable on Thursday. They are two separate operations, and separating them reduces risk enormously. We will look at this in 03-05.

Mistake 5: using long-lived branches and calling it CI. If your branch has 60 commits and two weeks of life, you will integrate badly even with a thousand green tests. Integration frequency is the practice; tests are the mechanism.

Tip 1: start by measuring, not by automating. Before writing your first workflow, write down how long your deployment takes today and how many times a month it fails. Without a baseline you will not be able to show the investment was worth it. Lesson 01-05 is about exactly this.

Tip 2: the pipeline is production code. It lives in the repository, it is reviewed in pull requests and it is versioned like everything else. A .github/workflows/ci.yml edited by hand through a web interface is technical debt from minute one.

Tip 3: if it hurts, do it more often. This is the principle that sums up the entire course. Merges hurt because they are rare; deployments are scary because they are exceptional. Increasing frequency forces you to automate, and automating removes the pain.

Exercises

Exercise 1: classify the situations

For each situation, say whether it describes continuous integration, continuous delivery, continuous deployment or none of the three, and justify your answer in one sentence.

  1. The team has a server that runs the tests every night at 3:00 against the main branch. Feature branches are merged every three weeks.
  2. Every pull request triggers a build and tests in 6 minutes. If they are green, it can be merged. Deployments are still manual, over SFTP.
  3. On merging to main, the pipeline builds a Docker image, deploys it to staging and runs smoke tests. Marta presses "Deploy to production" whenever she considers it appropriate.
  4. On merging to main, the pipeline deploys to production for 5% of traffic, watches errors for 10 minutes and, if all goes well, completes the deployment. Nobody intervenes.
  5. Diego builds on his laptop and uploads over SFTP on Fridays.

Exercise 2: spot the breaks in repeatability

The following pseudo-script is Diego's manual process. Identify at least four reasons why the resulting artifact is not reproducible, and propose a fix for each one.

#!/bin/bash
# deploy-friday.sh — Reservalia's current process
cd ~/projects/reservalia/apps/api
git pull
npm install
npm run build
echo "Version: $(date)" > dist/VERSION.txt
sftp diego@prod-server <<< "put -r dist/* /var/www/api/"
psql -h rds-prod -U admin -f migrations/latest.sql

Exercise 3: draw the target flow

Write a mermaid diagram of Reservalia's target pipeline with these conditions, using the terms trigger, stage, job, artifact, environment and promotion correctly:

  • It is triggered when merging to main.
  • A verification stage with two parallel jobs: test-api and test-web.
  • If both pass, one artifact is built, tagged with the commit SHA.
  • The artifact is deployed to staging automatically.
  • It is promoted to prod only after manual approval from Marta.

Solutions

Solution to Exercise 1

  1. None of the three. This is a nightly build. There is test automation, but branches live for three weeks: this is not continuous integration, it is late integration verified overnight. Verification arrives up to 21 days after the change that broke it.
  2. Continuous integration. Every change is verified automatically before it is integrated and the feedback cycle takes minutes. There is no continuous delivery because there is no deployable artifact and no automated deployment: deploying is still a manual process.
  3. Continuous delivery. The whole technical path to production is automated (build, artifact, staging, smoke tests) and only one manual gate remains: Marta's decision. This is the canonical example of delivery.
  4. Continuous deployment. There is no human intervention between the merge and production. The essential safety net is there too: progressive deployment (5% canary) and automated verification before completing.
  5. None of the three. This is manual deployment with late integration. It is Reservalia's starting point.

Solution to Exercise 2

# Problem Why it breaks repeatability Fix
1 The build happens in ~/projects/... on Diego's laptop The environment accumulates state: old node_modules, unversioned files, local variables, a personal Node version Build on an ephemeral runner that clones the repository from scratch
2 git pull without pinning a commit You do not know which exact commit was built; if someone pushes during the process, something else gets built Check out a specific SHA and use it as the artifact tag
3 npm install It can resolve to newer versions within the semver ranges and modify package-lock.json Use npm ci, which installs exactly the lock and fails if there are inconsistencies
4 Unpinned Node version Node 20.9 and Node 20.11 can produce different artifacts Pin the version in .nvmrc / engines and in the pipeline
5 echo "Version: $(date)" The timestamp means two builds of the same code produce different artifacts Tag with the commit SHA, not with the date
6 sftp put -r dist/* It is an incremental deployment: files deleted in the code stay alive on the server. The server accumulates history Deploy a complete immutable artifact (container image)
7 psql -f migrations/latest.sql by hand There is no control over which migrations have been applied, no guaranteed order and no way to revert A versioned migration tool, run by the pipeline (lesson 04-06)

Four would have been enough; if you found more, that is an even better sign.

Solution to Exercise 3

flowchart TD
    T(["TRIGGER: push / merge to main"]) --> V

    subgraph V["STAGE: verify"]
        direction LR
        J1["JOB: test-api"]
        J2["JOB: test-web"]
    end

    V -->|"both green"| B

    subgraph B["STAGE: build"]
        J3["JOB: build<br/>docker build -t reservalia/api:$SHA"]
    end

    B --> ART[("ARTIFACT<br/>reservalia/api:a3f9c21<br/>in ECR")]
    ART -->|"automatic deployment"| ENV1["ENVIRONMENT: staging"]
    ENV1 --> GATE{"manual gate:<br/>Marta approves"}
    GATE -->|"PROMOTION<br/>of the same artifact"| ENV2["ENVIRONMENT: prod"]

    style ART fill:#d9f2d9,stroke:#333,stroke-width:2px
    style GATE fill:#fff2cc,stroke:#333

Points that had to appear and are worth checking in your version:

  • The trigger is a repository event, not a manual action.
  • test-api and test-web are parallel jobs inside the same stage, not separate stages.
  • A single artifact is built, tagged with the SHA (not with the date, nor with "latest").
  • What goes to production is the same artifact that was validated in staging: that is promotion, not a rebuild.
  • The manual gate places this pipeline in continuous delivery. Remove that gate and it would be continuous deployment.

Conclusion

In this lesson we have laid the foundations of the course:

  • The enemy has two faces: integration hell (merging late and all at once) and manual deployment (processes that depend on one person not making a mistake). Reservalia suffers from both.
  • Continuous integration means integrating often and verifying automatically in a clean environment. It is a team practice before it is a tool.
  • Continuous delivery keeps software always deployable: the process is automated and the decision is human.
  • Continuous deployment removes that last manual gate, and it is only sensible with reliable tests, progressive deployment, monitoring and rollback.
  • The three stack up: no deployment without delivery, and no delivery without CI.
  • And we have a shared vocabulary: repository, commit, branch, trigger, pipeline, stage, job, runner, artifact, environment, promotion and reproducible build. Two golden rules we will repeat until the very end: build once and promote the same artifact, and pin versions so that the build is reproducible.

You now know what CI/CD is. Marta's logical question when she presents this to her team will be a different one: "and what does this give us, and what is it going to cost us?". Because setting up a pipeline is not free: it consumes configuration time, maintenance and execution minutes. In the next lesson, Benefits of CI/CD, we will look honestly at both sides of the scales: what a team like Reservalia's gains, how each benefit is measured, and in which situations CI/CD gives back less than it costs.

CI/CD Course: Continuous Integration and Deployment

Module 1: Introduction to CI/CD

Module 2: Continuous Integration (CI)

Module 3: Continuous Deployment (CD)

Module 4: Advanced CI/CD Practices

Module 5: Implementing CI/CD in Real Projects

Module 6: Tools and Technologies

Module 7: Practical Exercises

Module 8: Additional Resources

© Copyright 2026. All rights reserved