AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

CI/CD Pipelines: Automating Build and Deployment

📚 DevOps⏱️ 20 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Imagine you and a friend are building a ticket-booking website for your school's annual fest. You write the code that calculates the ticket price, your friend writes the code that shows the seating layout, and a third teammate writes the login page. Each of you is editing the same project on your own laptop. On the morning of the fest launch, you all copy your files onto the college server by hand, restart it, and hope nothing breaks. Two hours later, students start complaining that ticket prices are showing up negative. Somewhere in the last-minute copy-paste, a bug slipped through, nobody tested it before it went live, and now real users are seeing it before you do.

This is not a hypothetical problem invented for a textbook — it is the single most common way small software teams get hurt, and it is exactly the problem that CI/CD pipelines were invented to solve. CI/CD stands for Continuous Integration and Continuous Delivery/Deployment. Before we define those formally, let's understand precisely what goes wrong without them, because the fix only makes sense once you've felt the pain.

Why "just copy the files over" breaks down

When one person writes a small program, testing is simple: you run it, you look at the output, you fix what's wrong. But a real application like the fest ticket site is built by multiple people, each changing different parts of a shared codebase, multiple times a day. Every time someone's change is combined with everyone else's, three things can go wrong that a single developer working alone would never see:

  • The combined code might not even build. Your friend's seating-layout code might expect a function you renamed yesterday.
  • The combined code might build but behave incorrectly. Each person's part worked fine in isolation, but together they produce wrong output — like the negative ticket price.
  • Even correct code can fail during deployment — the step where you actually put the new version in front of real users. A missing configuration file, a server that wasn't restarted properly, or a database that wasn't updated can all cause a perfectly good piece of code to fail live.

Doing all of this checking by hand, every single time someone changes the code, does not scale. A team that merges code five times a day cannot afford to manually rebuild, retest, and redeploy the entire site five times a day — so in practice, teams without automation either skip testing (and ship bugs, like the negative price) or test rarely (and let bugs pile up until nobody can tell which change broke what). A CI/CD pipeline is the automation that removes this trade-off: it lets a team integrate and test changes constantly, and release working software just as often, without a human repeating the same manual steps every time.

Defining the pipeline: what actually happens, in order

A CI/CD pipeline is a fixed sequence of automated stages that every code change passes through, from the moment a developer saves their work to the moment it reaches real users. Each stage acts as a checkpoint: the code must pass one stage before it is allowed to move to the next. If any stage fails, the pipeline stops immediately and the change never reaches users in a broken state.

The diagram below shows the standard six-stage pipeline, and it also shows the one detail most explanations get vague about: where Continuous Delivery and Continuous Deployment actually diverge.

Continuous Deployment (fully automatic, no human click) 1. Commit git push to repo 2. Build install deps, compile 3. Run Tests pytest / unit tests 4. Deploy to staging server 5. Approval human clicks Deploy 6. Production live for real users Pipeline Stops developer notified, bug must be fixed pass Continuous Delivery (stops for a human click) fail fix code, commit again

Walk through the diagram left to right. Stage 1 is the trigger: a developer pushes a commit to the shared repository (on a platform like GitHub or GitLab). That push automatically wakes up the pipeline — nobody has to remember to click "start." Stage 2, Build, takes the raw source code and turns it into a runnable form: installing dependencies, compiling code if the language needs it (Java, C++), or simply checking that every file needed is present and importable. Stage 3, Test, runs the team's automated tests against that build. This is the checkpoint that catches logic errors like the negative ticket price, and it is the heart of Continuous Integration.

If testing fails, the pipeline halts right there — the red path in the diagram — and the developer is notified with the exact error, before any user ever sees the broken version. If testing passes, the build moves to Stage 4, deployment to a staging server: a private copy of the production environment where the team can do one last check with real-world conditions, but no real users are affected if something is still wrong. From there, the diagram shows two different possible paths to production, and this is where the "CD" in "CI/CD" actually splits into two distinct practices that are frequently confused.

Continuous Integration: testing every change, not just the "final" one

Continuous Integration (CI) is the practice of merging every developer's code into the shared project frequently — many times a day rather than once at the end of the project — and automatically building and testing that merged code every single time. The word "continuous" is doing real work here: it does not mean testing is running every second without a trigger; it means testing happens on every integration event, with no change small enough to skip it.

Let's trace exactly how this catches a bug, using a simplified version of the fest ticket-price function. Here is the correct version:

def apply_discount(price, is_member):
    discount = 0.10 if is_member else 0
    return price - (price * discount)

For a club member buying a ₹500 ticket: discount = 0.10, so the function returns 500 - (500 * 0.10) = 500 - 50 = 450. That's correct — a 10% discount.

Now suppose a teammate, in a hurry before the fest, edits this function and accidentally writes the discount as a whole number instead of a decimal fraction — a classic off-by-a-decimal-point mistake:

def apply_discount(price, is_member):
    discount = 10 if is_member else 0   # bug: should be 0.10
    return price - (price * discount)

Trace it by hand with the same input, price = 500, is_member = True: discount = 10, so the function returns 500 - (500 * 10) = 500 - 5000 = -4500. A ticket that should cost ₹450 now costs −₹4,500 — the exact "negative price" bug from the opening scenario.

Without CI, this change gets merged, deployed, and discovered only when a student sees a negative number on the payment screen. With CI, the team has already written an automated test file that runs on every commit:

def test_member_gets_ten_percent_off():
    assert apply_discount(500, True) == 450

def test_non_member_pays_full_price():
    assert apply_discount(500, False) == 500

Trace what happens when the buggy version is committed. test_non_member_pays_full_price still passes — with is_member = False, discount = 0 in both the correct and buggy versions, so the output is 500 either way, and this test cannot tell them apart. But test_member_gets_ten_percent_off calls apply_discount(500, True), gets -4500, compares it against the expected 450, and the assert statement fails. The build stage in the pipeline reports "1 test failed," the pipeline stops at Stage 3, and the code never reaches staging or production. The developer sees the failure within minutes of pushing the commit — not weeks later from a confused user's complaint.

Notice something important in that trace: a passing test suite only proves what it actually checks. If the team had only written the non-member test, this exact bug would have sailed straight through CI undetected, because that test happens to be blind to it. This is the first common misconception worth naming directly: CI does not mean "the code is now guaranteed correct." It means "the code passed every test we wrote for it." A pipeline is only as good as the tests inside it — automation removes the burden of running tests by hand, but a human still has to think of the right tests to write.

Continuous Delivery vs. Continuous Deployment: the misconception that trips up almost everyone

Here is the second, and probably most common, misconception about this topic: many learners assume "CD" only ever means one thing. It actually refers to two related but distinct practices, and the diagram's Stage 5 is exactly where they diverge.

Continuous Delivery means every change that passes CI is automatically built, tested, and packaged into a ready-to-release state — but a human still makes the final decision to actually push it to production, usually by clicking a "Deploy" button. This is the yellow, dashed-border box in the diagram: the pipeline does everything up to the edge of production and then waits.

Continuous Deployment goes one step further: if the code passes every automated stage, it goes live automatically, with no human in the loop at all. This is the purple curved path in the diagram, skipping straight from the staging deployment to production.

Both practices share the same CI foundation and the same pipeline stages 1 through 4. The only difference is whether Stage 5 exists as a human checkpoint or is removed entirely. Teams choose Continuous Delivery when the cost of a bad release is high enough that they want a person to make the final call — for example, a banking or ticket-payment system, or any release timed to coincide with an event like a fest going live. Teams choose full Continuous Deployment when releases are low-risk and reversible, and they would rather get fixes and features to users within minutes of a commit being merged, without waiting for someone to be free to click a button.

Reading a real pipeline definition

In practice, a team doesn't draw this pipeline by hand — they write it as a configuration file that a CI/CD tool (GitHub Actions, GitLab CI, or Jenkins are common ones) reads and executes automatically on every push. Here is what Stages 1–3 of our fest ticket example look like as a real GitHub Actions file, typically saved at .github/workflows/pipeline.yml:

name: CI Pipeline
on: [push]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest test_pricing.py

Reading this line by line: on: [push] is the trigger — this whole job runs automatically every time anyone pushes a commit, matching Stage 1. runs-on: ubuntu-latest tells the platform to spin up a fresh Linux machine for this job, so every test runs in an identical, clean environment rather than "it works on my laptop." The steps then run in strict order: check out the latest code, install Python, install the project's dependencies, and finally run pytest against the test file — Stages 2 and 3 of our diagram, expressed as commands. If the pytest command exits with any failure (exactly like our discount bug would trigger), GitHub Actions marks the whole job as failed and — this is the automation payoff — automatically blocks that change from being merged into the main branch, without anyone having to remember to check.

Adding a second job that only runs after the first one succeeds extends this into Stage 4 and beyond:

  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to server
        run: ./deploy.sh

The line needs: build-and-test is what enforces the checkpoint structure from the diagram — this job is not allowed to start until the build-and-test job has finished successfully. The line environment: production is a real GitHub Actions feature that can be configured to require a specific person's approval before the job proceeds — that's Stage 5's approval gate, implemented directly in the configuration. Delete that environment requirement, and the exact same file becomes Continuous Deployment instead of Continuous Delivery — nothing else in the pipeline needs to change. This is worth sitting with: the difference between the two practices your textbook may present as conceptually distant is, in a real tool, a single configuration line.

Why this matters beyond a fest website

Scale the fest ticket-booking scenario up to something like a national ticket-booking system used during a high-demand booking window, where lakhs of people are trying to book in the same few minutes. A bug pushed straight to production without any automated check, at exactly that moment, would be far more damaging than a few confused fest attendees — it could stop a large number of real transactions at the one time they matter most. A pipeline doesn't make bugs impossible; it makes sure that the specific kinds of bugs a team has learned to test for cannot reach real users silently, and it does this at a speed and consistency no group of humans manually re-testing the same checklist could sustain across dozens of changes a day.

This connects directly to the Software Development Life Cycle you may already have seen drawn as a one-way sequence: Design → Code → Test → Deploy → Maintain. A CI/CD pipeline is what that same loop looks like when a real team runs it dozens of times a day instead of once per project — the stages don't change, but "Test" and "Deploy" stop being manual steps someone remembers to do and become automated gates the code cannot bypass.

Check your understanding

  1. In the pipeline diagram, if test_member_gets_ten_percent_off() fails, which stage does the pipeline stop at, and does the buggy code ever reach the staging server?
  2. Trace apply_discount(1000, True) by hand for the buggy version (discount = 10). What value does it return, and why is a human likely to notice this particular bug immediately even without a test?
  3. A classmate says, "Our project uses GitHub, so we're already doing CI/CD." Explain precisely what is missing from that statement.
  4. Explain, using the environment: production line from the YAML file, exactly what would need to change to turn a Continuous Delivery pipeline into a Continuous Deployment pipeline.
  5. Why did test_non_member_pays_full_price() pass even in the buggy version? What does this reveal about the limits of automated testing?

Answers: (1) The pipeline stops at Stage 3, Run Tests — the red "fail" path in the diagram — so the buggy code never reaches Stage 4 (staging) or Stage 6 (production). (2) 1000 - (1000 * 10) = 1000 - 10000 = -9000; a human would likely notice this specific case immediately because a negative price is an obviously absurd number on a checkout screen — but this doesn't help before deployment, since by then real users are already seeing it, which is exactly why an automated test catching it before deployment matters. (3) GitHub is a code-hosting platform for storing and merging code; it does not automatically build, test, or deploy anything by itself. CI/CD requires a separate pipeline configuration (like a GitHub Actions YAML file) that defines the build, test, and deploy stages — hosting the code and automating checks on it are two different things. (4) Remove (or stop requiring reviewers on) the environment: production line/protection rule on the deploy job, so the job runs automatically as soon as build-and-test succeeds, with no human approval step in between. (5) Because with is_member = False, discount equals 0 in both the correct and buggy versions, making the function's output identical (500) regardless of which version is running — the test's inputs happen to be blind to this particular bug. This shows that a passing test suite only proves the specific cases it checks; it never proves the code is correct for cases nobody thought to test.

Summary

A CI/CD pipeline is an ordered sequence of automated checkpoints — build, test, deploy to staging, and deploy to production — that every code change must pass through before reaching real users, replacing the error-prone habit of manually copying files onto a server and hoping for the best. Continuous Integration is specifically the practice of merging and automatically testing code frequently, so bugs like a misplaced decimal point in a discount calculation are caught within minutes of being written rather than after real users encounter them — but a test suite is only as strong as the tests a team actually writes, and a passing suite proves only what it checks. Continuous Delivery and Continuous Deployment share the same pipeline up through staging; they differ only in whether a human clicks "Deploy" as the final gate or whether the pipeline is trusted to push straight to production on its own, and in real tools like GitHub Actions this distinction can come down to a single configuration line. None of this eliminates the need for good tests or careful engineering — it simply guarantees that whatever checks a team has built are actually run, every time, without depending on a human remembering to do it by hand.

← Kubernetes: Container Orchestration at ScaleGitHub Actions: Workflow Automation →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn