Skip to main content

🔄 CI/CD Concepts

You've written tests. Now imagine those tests running automatically — every push, every pull request, on a clean machine that doesn't care what's cached on your laptop. That's the heart of CI/CD: a robot teammate that builds, tests, and ships your code so humans don't have to remember to.

Week 12 · Day 5 (Friday: Continuous Integration) · Lecture 1

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Define continuous integration and explain the specific problem it solves
  • Distinguish continuous delivery from continuous deployment — the two very different "CD"s
  • Trace a change through the canonical lint → test → build → deploy pipeline stages
  • Identify the essential components of a pipeline: source control, a CI runner, tests, artifacts, and environments
  • Compare deployment strategies (blue-green, canary, rolling) and when each reduces risk
  • Recognize why small, frequent, automatically-tested merges beat big-bang integrations

Estimated Time: 60 minutes

Practice: Design a pipeline on paper and sketch the stages your own project would need.

In This Lesson

The "Works on My Machine" Problem

Every developer has said it, usually while staring at a red build: "But it works on my machine!" The trouble is that your machine has your node version, your environment variables, your half-committed files, and a folder full of cached dependencies nobody else has. When five developers each integrate a week of separate work at once, the merge is a minefield — and the bugs only surface when everything collides.

Continuous Integration and Continuous Delivery/Deployment (CI/CD) is the industry's answer. Instead of integrating rarely and manually, you integrate constantly and let automation do the checking. A neutral server pulls each change, installs dependencies from scratch, runs the whole test suite, and tells you within minutes whether the change is safe. If deployment is wired up, that same server can ship the passing code to real users without a human touching a terminal.

graph LR A["Write code locally"] --> B["Push to shared repo"] B --> C["CI server checks it out"] C --> D["Install, lint, test, build"] D --> E{"All green?"} E -->|Yes| F["Ready to deliver"] E -->|No| G["Fast feedback to author"] G --> A

The payoff is not just fewer bugs. It's confidence: a green checkmark on a pull request means the change survived a repeatable, honest gauntlet — not just "it seemed fine when I tried it."

What Continuous Integration Really Is

Continuous Integration (CI) is the practice of merging every developer's work into a shared main branch frequently — ideally many times a day — with each merge automatically built and tested. The word doing the heavy lifting is automatically. CI is not "we use GitHub." CI is "every push triggers a build and a test run, and a broken build is treated as a stop-the-line emergency."

📖 The original definition

Martin Fowler, who popularized the term, describes CI as a practice where team members integrate their work frequently, "each integration is verified by an automated build (including test) to detect integration errors as quickly as possible." The two pillars are frequency and automated verification — neither works without the other.

Think of a busy kitchen. In a bad kitchen, cooks each prepare a full dish in isolation and only combine everything at the very end — where clashing flavors and missing ingredients are discovered too late. In a CI kitchen, every component is tasted the moment it's added, so a mistake is caught while it's a single spoonful, not a ruined banquet.

What CI gives you

BenefitWhy it matters
Early bug detectionA failing test on push is 100x cheaper to fix than a bug in production
Smaller integrationsMerging daily means each merge is tiny and easy to reason about
Always-shippable mainBecause main is continuously verified, it stays deployable
Fast feedbackAuthors learn within minutes, while the code is still fresh in their heads
Shared responsibilityA red build is everyone's problem, which builds a quality culture

Delivery vs. Deployment: The Two CDs

Here's the distinction that trips up almost everyone. "CD" stands for two different things, and the difference is one human click.

Continuous Delivery

Every change that passes the pipeline is automatically prepared and ready to release to production — the artifact is built, tested, and sitting in staging. But the final push to production waits for a human to approve it. You could ship any commit at the press of a button; you choose when.

Continuous Deployment

Goes one step further: there is no manual gate. Every change that passes every stage of the pipeline is automatically released to production. The pipeline itself is trusted enough that a green run means "live for users," full stop.

The mnemonic: Delivery stops at the deli counter waiting for you to order; Deployment sends it straight to the door.

graph TD A["Commit passes CI"] --> B["Build artifact"] B --> C["Auto-deploy to staging"] C --> D["Automated acceptance tests"] D --> E{"Which CD?"} E -->|Continuous Delivery| F["Wait for human approval"] F --> G["Release to production"] E -->|Continuous Deployment| G

⚠️ Which should you choose?

Continuous deployment demands mature testing, monitoring, and easy rollback — because a bad commit reaches users with no human safety net. Most teams start with continuous delivery (automation up to a manual "go" button) and graduate to full deployment only once they trust the pipeline. Both sit on top of solid CI; neither is possible without it.

The Pipeline Stages

A pipeline is a sequence of automated stages a change must pass, in order, from commit to production. Each stage is a checkpoint: if it fails, the pipeline stops and the change never advances. The canonical ordering runs the fast, cheap checks first so failures surface quickly.

The commit to deploy pipeline: lint, test, build, then deploy Commit Lint seconds Test minutes Build artifact Deploy
Cheap checks first: linting runs in seconds, so a style error fails the build before you've paid for a slow test run.

Stage by stage

  • Source — a push or pull request triggers everything. The runner checks out the exact commit.
  • Lint — static analysis (ESLint, Prettier, type-checking). Fast, catches style and obvious errors first.
  • Test — unit tests, then integration tests. This is where correctness is verified.
  • Build — compile/bundle the app into a deployable artifact (a build folder, a Docker image).
  • Deploy — ship the artifact to staging, run acceptance tests, and (after delivery approval or automatically) to production.

🔑 Build once, deploy many

A golden rule: build the artifact one time, then promote that exact same artifact through staging and into production. If you rebuild for each environment, you're testing one thing and shipping another. Same artifact everywhere = what you tested is what users get.

Anatomy of a Pipeline

Every CI/CD setup, whatever the tool, is assembled from the same building blocks. Recognizing them makes any platform — GitHub Actions, GitLab CI, CircleCI, Jenkins — feel familiar.

ComponentJobCommon tools
Source controlStores code, tracks changes, fires triggersGit on GitHub, GitLab, Bitbucket
CI runnerA clean VM that executes the pipelineGitHub Actions, Jenkins, CircleCI
Test frameworkRuns the checks that gate the changeJest, Vitest, Playwright, Cypress
Artifact storageHolds build outputs between stagesActions artifacts, Docker Hub, npm
EnvironmentsStaging and production targetsNetlify, Vercel, AWS, Kubernetes
MonitoringWatches the app after deploySentry, Datadog, Grafana

A quick sketch of a real Node.js CI job — you'll build these for real in the next lesson, but here's the shape so the vocabulary lands:

# .github/workflows/ci.yml — a first taste
name: CI
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  verify:
    runs-on: ubuntu-latest          # a clean, throwaway Ubuntu VM
    steps:
      - uses: actions/checkout@v4    # 1. get the code
      - uses: actions/setup-node@v4  # 2. install Node
        with:
          node-version: '20'
          cache: 'npm'               # cache deps to speed up runs
      - run: npm ci                  # 3. install exactly what's locked
      - run: npm run lint            # 4. lint  (fast checks first)
      - run: npm test                # 5. test
      - run: npm run build           # 6. build the artifact

Notice how the YAML mirrors the stages we just walked through: check out, set up, install, lint, test, build. The CI server does in a clean room exactly what a careful developer would do by hand — but the same way, every single time.

Deployment Strategies

Once a pipeline can deploy automatically, how it releases matters. Pushing new code to every server at once is fast but risky — one bad build takes everyone down. Smarter strategies trade a little complexity for a lot of safety.

Blue-green

Run two identical production environments, "blue" (live) and "green" (idle). Deploy the new version to green, test it fully, then flip the load balancer so all traffic goes to green. If anything breaks, flip back to blue instantly. Zero-downtime, instant rollback — at the cost of running double the infrastructure during a release.

Canary

Named after the canary in a coal mine. Route a small slice of traffic — say 5% — to the new version while everyone else stays on the old one. Watch the error rates. If the canary stays healthy, gradually shift more traffic; if it struggles, pull it back before most users ever notice.

graph TD A["Load balancer"] --> B["95 percent to stable version"] A --> C["5 percent to new version"] C --> D["Monitor error rates"] D --> E{"Canary healthy?"} E -->|Yes| F["Shift more traffic over"] E -->|No| G["Roll back the canary"]

Rolling

Update instances a few at a time — replace instance 1, wait, replace instance 2, and so on — until the fleet is fully upgraded. No extra infrastructure like blue-green, and no total outage, but for a moment old and new versions serve traffic side by side, so they must stay compatible.

💡 Feature flags: decouple deploy from release

A complementary trick: ship code to production but keep the new feature hidden behind a flag (an if that reads a config value). Now deploying the code and releasing the feature are separate events. You can turn a feature on for 1% of users, run an A/B test, or kill a misbehaving feature instantly — no redeploy required.

Practice & Quiz

🏋️ Exercise 1: Order the stages

Goal: A teammate proposes this pipeline order. It has a costly mistake. Identify what's wrong and rewrite the order.

# Proposed (flawed) pipeline order:
1. Run full end-to-end test suite (12 minutes)
2. Lint the code (5 seconds)
3. Build the application
4. Run unit tests (30 seconds)
5. Deploy to production
💡 Hint

Which stages are cheap and which are expensive? A "fail fast" pipeline runs the checks most likely to fail — and quickest to run — first, so you don't burn 12 minutes only to fail on a missing semicolon.

✅ Solution
# Corrected: cheap + likely-to-fail checks first
1. Lint the code (5 seconds)          # catches style/syntax instantly
2. Run unit tests (30 seconds)        # fast correctness check
3. Build the application              # only build if code is sound
4. Run full end-to-end suite (12 min) # slow, run only on a good build
5. Deploy to production               # last, after everything is green

The flaw was running the slowest stage first. A linting error would waste 12 minutes before failing. Ordering by speed gives developers feedback in seconds for the most common mistakes.

🏋️ Exercise 2: Delivery or deployment?

Goal: For each scenario, decide whether it describes continuous delivery or continuous deployment.

  1. Every passing commit lands in staging; a release manager clicks "Promote" to send it live.
  2. A merge to main that passes all tests is serving real users eight minutes later, no human involved.
  3. The pipeline builds and tests every change but a Slack approval is required before production.
✅ Solution

1. Delivery — automation stops at a human "Promote" gate.
2. Deployment — fully automatic all the way to production.
3. Delivery — the required approval means it's not automatic; a human gates the release.

The single deciding question: does a human have to approve the production release? Yes = delivery. No = deployment.

🎯 Quick Quiz

Question 1: What is the defining feature of Continuous Integration?

Question 2: The only difference between continuous delivery and continuous deployment is:

Question 3: Why should a pipeline lint before it runs slow end-to-end tests?

Best Practices & Pitfalls

✅ Do

  • Commit small and often — integrate daily so merges stay tiny and conflicts stay trivial
  • Treat a red build as a stop-the-line emergency; fix it before starting new work
  • Keep the pipeline fast (cache dependencies, parallelize) so feedback stays under ~10 minutes
  • Build the artifact once and promote the same one through every environment
  • Store pipeline config in the repo (pipeline-as-code) so it's versioned and reviewed like any code

❌ Don't

  • Don't let CI stay red "for now" — a normally-broken build trains people to ignore it
  • Don't rebuild separately for staging and production; you'd ship something you never tested
  • Don't skip tests to "unblock" a deploy — that defeats the entire purpose of the pipeline
  • Don't put continuous deployment in place before you have solid monitoring and easy rollback

⚠️ The flaky-test trap

A test that passes and fails randomly without code changes is flaky. Flaky tests are corrosive: they train the team to hit "re-run" and ignore red builds, which eventually hides a real failure. Quarantine flaky tests, fix the root cause (usually timing or shared state), and don't let "just re-run it" become a habit.

Summary

🎉 Key Takeaways

  • CI = merge frequently, and automatically build + test every change to catch integration problems early
  • Continuous delivery keeps a manual approval before production; continuous deployment removes it — the difference is one human click
  • The canonical pipeline runs lint → test → build → deploy, cheapest checks first, to fail fast
  • Every pipeline is built from the same parts: source control, a runner, tests, artifacts, environments, monitoring
  • Build once, deploy many — promote the exact artifact you tested
  • Blue-green, canary, and rolling deploys plus feature flags shrink the blast radius of a bad release

📚 Additional Resources

🚀 What's Next?

You now understand what a pipeline is and why it exists. Next, you'll build one for real: the next lesson, GitHub Actions for CI, walks through workflow YAML — triggers, jobs, runners, and steps — and gets your first CI pipeline running on an actual repository.

🔄 The robot teammate awaits

You've got the mental model. Time to teach a machine to build and test your code on every push.