| |

LFCA 83 🐧 Continuous Integration Explained

Continuous Integration is the practice of merging code changes into a shared repository frequently, and verifying each merge with an automated build and test. It is the first half of CI/CD, and it is the foundation that makes Continuous Delivery possible. Without CI, the rest of the pipeline has nothing reliable to build on.

The LFCA exam places CI under DevOps Fundamentals, which carries 12–16% of the total weight. The study plan lists “DevOps Basics” and “Deployment Environments” as core topics. The exam expects you to understand what CI is, why it matters, what happens in a CI pipeline, and how it differs from the older practice of integrating code only at release time.

Key point: Continuous Integration is not a tool. It is a practice. The practice is: merge to trunk at least daily, run the build and tests automatically on every merge, and never commit code that does not pass. Tools like Jenkins, GitHub Actions, and GitLab CI support the practice, but they do not define it. A team that has a CI server but merges once a month is not doing CI.


Why Continuous Integration exists

Before CI, developers worked on long-lived feature branches. The integration of those branches happened at the end of a release cycle, and it was painful. The code had diverged. The conflicts were complex. The bugs caused by the integration took days to resolve.

The integration hell problem. A team of five developers each works on a separate feature for three months. At the end of the quarter, they merge all five branches into the main branch. The merge produces hundreds of conflicts. The combined code has bugs that none of the individual branches had. The integration takes two weeks and delays the release. This is “integration hell,” and it is the problem CI was designed to solve .

The feedback problem. A developer writes a function, commits it, and moves on to the next task. If the function has a bug, the developer does not find out until the integration phase months later. By then, the developer has forgotten the details. CI provides feedback within minutes of the commit, while the change is still fresh .

The confidence problem. Without CI, no one knows whether the code on the main branch actually works. The build might be broken. The tests might be failing. The only way to find out is to try to build it, and that might be a manual process. CI ensures that the main branch is always in a known-good state. A passing pipeline means the code builds and the tests pass .

The automation problem. A manual integration process depends on the discipline of the developers. Someone must remember to run the tests. Someone must remember to check the build. In practice, this does not happen. CI automates the process. The pipeline runs on every push, and the result is visible to everyone.

The trade-off. CI requires discipline. Developers must merge frequently, which means they must break their work into small, independent pieces. They must write tests that run in the pipeline. They must fix the pipeline when it breaks, even if the break is not their fault. The discipline is the cost. The payoff is a codebase that is always in a known-good state and a team that can release with confidence.


a. The CI Workflow

The CI workflow is a sequence of events that starts when a developer pushes code and ends when the pipeline reports success or failure. The workflow is the same whether the team uses GitHub Actions, GitLab CI, Jenkins, or any other CI tool.

Commit. The developer writes code on a short-lived branch. The branch is created from the main branch, and it lives for hours or days, not weeks. The developer commits the changes with a clear message.

Push. The developer pushes the branch to the shared repository. The push triggers the CI pipeline. The CI tool detects the new commit and starts the build.

Build. The pipeline checks out the code at the specific commit. It installs the dependencies. It compiles the code. If the build fails, the pipeline stops, and the developer is notified. The build must be reproducible: the same source produces the same artifact every time.

Test. The pipeline runs the automated tests. The tests include unit tests, integration tests, and sometimes end-to-end tests. The tests are ordered from fastest to slowest so that the pipeline fails early if something is wrong. A failing test fails the pipeline.

Static Analysis. The pipeline runs the linters, the security scanners, and the dependency checkers. These tools catch issues that the tests do not: style violations, known vulnerabilities, outdated dependencies. The results are reported to the developer.

Package. The pipeline builds the artifact. The artifact might be a Docker image, a JAR file, a ZIP archive, or a binary. The artifact is versioned with the commit SHA or a semantic version. It is stored in an artifact registry.

Report. The pipeline reports success or failure. If it succeeds, the code is ready to be merged. If it fails, the developer fixes the issue and pushes again. The pipeline runs again on the new commit.

Merge. When the pipeline passes and the code is reviewed, the branch is merged into the main branch. The merge triggers the pipeline again on the main branch. This ensures that the main branch is always in a known-good state.

The workflow is not a single step. It is a cycle that repeats with every commit. The goal is to keep the cycle short. A pipeline that takes an hour to run does not provide timely feedback. A pipeline that takes five minutes does.


b. The CI Pipeline Stages

A CI pipeline is a sequence of stages that run in order. Each stage is a gate. If a stage fails, the pipeline stops, and the later stages do not run.

Source. The first stage checks out the code from the repository at the specific commit. The commit SHA is the identifier. The source stage ensures that the pipeline is building the exact code that was pushed.

Build. The build stage compiles the code and produces an artifact. The build is deterministic. The same source produces the same artifact. The artifact is stored for later stages.

Test. The test stage runs the automated tests. The tests are organized into suites: unit tests, integration tests, and end-to-end tests. The unit tests run first because they are fast. The integration tests run next because they require external dependencies. The end-to-end tests run last because they are slow.

Lint. The lint stage runs the static analysis tools. The linters check for style violations. The security scanners check for known vulnerabilities. The dependency checkers verify that the dependencies are up to date and free of known issues.

Package. The package stage creates the deployable artifact. The artifact is versioned and stored in the registry. The artifact is immutable: it is never modified after it is created.

Publish. The publish stage makes the artifact available for deployment. The artifact is pushed to the registry. The registry is the handoff point between CI and CD.

Not every pipeline has every stage. A small project might combine build and test into a single stage. A large project might have separate stages for different types of tests. The principle is the same: each stage verifies something, and a failure in any stage stops the pipeline.


c. CI Best Practices

The practices that make CI effective are not about the tools. They are about how the team works.

Commit frequently. A developer who commits once a day integrates once a day. A developer who commits once a week integrates once a week. The longer the gap, the harder the integration. The recommendation is to commit at least daily, and to merge to trunk at least daily .

Keep the build fast. A build that takes an hour does not provide timely feedback. The developer moves on to another task, and the feedback arrives when the context is lost. The goal is to keep the build under ten minutes. The techniques are: parallelize the stages, cache the dependencies, and run the slow tests separately.

Write reliable tests. A flaky test that fails intermittently erodes trust in the pipeline. The team starts ignoring the failures, and the pipeline becomes noise. Flaky tests must be fixed or removed. Test reliability is real engineering work.

Fix the build immediately. If the pipeline fails, the team stops and fixes it. The main branch must always be in a known-good state. If the build stays broken, developers cannot merge, and the pipeline loses its value.

Make the pipeline visible. The pipeline status should be visible to the whole team. A dashboard, a Slack notification, or a build light. The visibility creates accountability. A broken build is everyone’s problem.

Treat the pipeline as code. The pipeline configuration is stored in the repository, versioned, and reviewed. It is not configured through a web UI. The configuration is code, and it is subject to the same standards as the application code.


Complete Example Session

This session demonstrates a CI pipeline with multiple stages, a failing test, and the fix that makes the pipeline pass.

# ============================================
# PART 1: THE PROJECT
# ============================================

# A Node.js application with a simple function and a test.

# src/calculator.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

# src/calculator.test.js
import { add, subtract } from './calculator';

test('add returns the sum', () => {
  expect(add(2, 3)).toBe(5);
});

test('subtract returns the difference', () => {
  expect(subtract(5, 3)).toBe(2);
});

# ============================================
# PART 2: THE CI PIPELINE
# ============================================

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, 'feature/**']
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run unit tests
        run: npm test -- --coverage

      - name: Build application
        run: npm run build

      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

The pipeline runs on every push to the main branch and feature branches. It also runs on every pull request to the main branch. The pipeline has stages for installing dependencies, running the linter, running the tests, and building the application.

# ============================================
# PART 3: THE FAILING TEST
# ============================================

# The developer introduces a bug.
# src/calculator.js
export function add(a, b) {
  return a - b;  // ❌ wrong operator
}

# The developer pushes the code.
git add .
git commit -m "Add calculator functions"
git push origin feature/calculator

# The CI pipeline triggers.
# The test stage fails.
# The pipeline reports:
# FAIL src/calculator.test.js
#   ● add returns the sum
#     expect(received).toBe(expected)
#     Expected: 5
#     Received: -1

# The developer is notified.
# The bug is caught before the code is merged.

The failing test is the feedback. The developer sees the failure within minutes of the push. The bug is fixed before the code is merged into the main branch.

# ============================================
# PART 4: THE FIX
# ============================================

# The developer fixes the bug.
# src/calculator.js
export function add(a, b) {
  return a + b;  // ✅ correct
}

# The developer pushes the fix.
git add .
git commit -m "Fix add function"
git push origin feature/calculator

# The CI pipeline triggers again.
# All stages pass.
# The pipeline reports success.

The pipeline is green. The code is ready for review and merge.

# ============================================
# PART 5: THE MERGE
# ============================================

# The pull request is reviewed and approved.
# The branch is merged into main.
git checkout main
git merge feature/calculator
git push origin main

# The CI pipeline triggers on the main branch.
# All stages pass again.
# The main branch is in a known-good state.

The merge triggers the pipeline on the main branch. This ensures that the main branch is always verified. The pipeline is the gate that keeps the main branch healthy.


Quick Reference

The CI Pipeline Stages

StagePurpose
SourceCheckout code at the commit
BuildCompile and package
TestRun automated tests
LintStatic analysis
PackageCreate the artifact
PublishStore the artifact

The CI Best Practices

PracticeWhy
Commit frequentlySmaller changes are easier to integrate
Keep the build fastTimely feedback
Write reliable testsTrust in the pipeline
Fix the build immediatelyMain branch stays healthy
Make the pipeline visibleAccountability
Treat the pipeline as codeVersioned and reviewed

The CI vs Traditional Integration

AspectTraditionalCI
Merge frequencyEnd of release cycleMultiple times per day
FeedbackWeeks or monthsMinutes
Integration painHighLow
Main branch stateUnknownKnown-good
Build processManualAutomated

The CI Tools

ToolType
GitHub ActionsCloud, integrated with GitHub
GitLab CICloud, integrated with GitLab
JenkinsSelf-hosted, extensible
CircleCICloud
Travis CICloud
Azure PipelinesCloud, integrated with Azure DevOps

Best Practices

✅ Do This:

# Run CI on every push and pull request
on:
  push:
    branches: [main, 'feature/**']
  pull_request:
    branches: [main]                                             # ✅
# Use npm ci instead of npm install for reproducible builds
npm ci                                                           # ✅
# Cache dependencies to speed up the pipeline
cache: 'npm'                                                     # ✅
# Run the linter before the tests
- run: npm run lint
- run: npm test                                                   # ✅
# Fix the build immediately when it fails
git commit -m "Fix broken build"                                 # ✅

❌ Don’t Do This:

# Don't merge long-lived branches
git checkout -b feature/big-rewrite  # lives for months           # ❌
# Don't skip tests for speed
- run: npm test -- --passWithNoTests                              # ❌
# Don't ignore flaky tests
# A flaky test is a bug in the test.                             # ❌
# Don't configure the pipeline through the UI
# Store the configuration in the repository.                     # ❌
# Don't commit directly to main
git push origin main  # bypasses the pull request                 # ❌

Common Pitfalls

PitfallWhy It HappensFix
Slow pipelineToo many stages, no cachingParallelize, cache dependencies
Flaky testsNon-deterministic testsFix or remove the test
Broken main branchBuild not fixed immediatelyStop and fix the build
Long-lived branchesFear of mergingCommit frequently
Pipeline ignoredNo visibilityMake the status visible

Real-World Examples

1. CI Trigger

on: [push, pull_request]

2. Install Dependencies

npm ci

3. Run Linter

npm run lint

4. Run Tests

npm test

5. Build Application

npm run build

6. Upload Artifact

uses: actions/upload-artifact@v4

7. Cache Dependencies

cache: 'npm'

8. Fix the Build

git commit -m "Fix broken build"

9. Merge to Main

git merge feature/calculator

10. Pipeline Status

# Green = passing
# Red = failing

Visual

The CI Workflow

┌──────────────────────────────────────────────┐
│  CI WORKFLOW                                 │
│                                              │
│  Developer commits                           │
│       │                                      │
│       ▼                                      │
│  Push to repository                          │
│       │                                      │
│       ▼                                      │
│  CI pipeline triggers                        │
│       │                                      │
│       ├─ Source: checkout                    │
│       ├─ Build: compile                      │
│       ├─ Test: run tests                     │
│       ├─ Lint: static analysis               │
│       ├─ Package: create artifact            │
│       └─ Publish: store artifact             │
│       │                                      │
│       ▼                                      │
│  Success or failure                          │
│       │                                      │
│       ├─ Failure → notify developer          │
│       └─ Success → ready to merge            │
│                                              │
└──────────────────────────────────────────────┘

The CI Pipeline Stages

┌──────────────────────────────────────────────┐
│  CI PIPELINE STAGES                          │
│                                              │
│  Source ──> Build ──> Test ──> Lint ──> Publish│
│                                              │
│  Each stage is a gate.                       │
│  If a stage fails, the pipeline stops.       │
│                                              │
│  The artifact is built once.                 │
│  The same artifact is used everywhere.       │
│                                              │
└──────────────────────────────────────────────┘

The CI vs Traditional

┌──────────────────────────────────────────────┐
│  TRADITIONAL                                 │
│    Dev 1 ──┐                                 │
│    Dev 2 ──┼──> Long-lived branches          │
│    Dev 3 ──┘         │                       │
│                      ▼                       │
│                 Integration hell             │
│                      │                       │
│                      ▼                       │
│                 Release (weeks)              │
│                                              │
│  CI                                          │
│    Dev 1 ──┐                                 │
│    Dev 2 ──┼──> Trunk (daily)                │
│    Dev 3 ──┘         │                       │
│                      ▼                       │
│                 Automated build + test       │
│                      │                       │
│                      ▼                       │
│                 Feedback (minutes)           │
│                                              │
└──────────────────────────────────────────────┘

The CI Best Practices

┌──────────────────────────────────────────────┐
│  CI BEST PRACTICES                           │
│                                              │
│  Commit frequently:                          │
│    └─ Merge to trunk daily                   │
│                                              │
│  Keep the build fast:                        │
│    └─ Under 10 minutes                       │
│                                              │
│  Write reliable tests:                       │
│    └─ Fix flaky tests                        │
│                                              │
│  Fix the build immediately:                  │
│    └─ Main branch stays healthy              │
│                                              │
│  Make the pipeline visible:                  │
│    └─ Dashboard or notification              │
│                                              │
│  Treat the pipeline as code:                 │
│    └─ Versioned and reviewed                 │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
CI definitionMerge frequently, verify with automated build and test
WorkflowCommit, push, build, test, lint, package, merge
Pipeline stagesSource, Build, Test, Lint, Package, Publish
Best practicesCommit frequently, fast build, reliable tests, fix immediately
Feedback loopMinutes, not weeks
Main branch stateAlways known-good
ArtifactBuilt once, deployed everywhere
ToolsGitHub Actions, GitLab CI, Jenkins, CircleCI
LFCA weightDevOps Fundamentals, 12–16%

Key takeaways:

  • Continuous Integration is the practice of merging code changes into a shared repository frequently. Each merge triggers an automated build and test. The goal is to detect integration errors quickly .
  • CI is not a tool. It is a practice. The tools support the practice, but they do not define it. A team that has a CI server but merges once a month is not doing CI.
  • The CI workflow is a cycle: commit, push, build, test, lint, package, merge. The cycle repeats with every commit. The goal is to keep the cycle short so that feedback arrives while the change is still fresh .
  • The pipeline stages are gates. Each stage verifies something. If a stage fails, the pipeline stops, and the developer is notified. The later stages do not run.
  • The best practices are about discipline. Commit frequently. Keep the build fast. Write reliable tests. Fix the build immediately. Make the pipeline visible. Treat the pipeline as code.
  • The artifact is built once and deployed everywhere. The same artifact that passes the CI pipeline is the one that goes to staging and production. It is never rebuilt for a different environment.
  • The LFCA exam tests the difference between CI and traditional integration. Traditional integration happens at the end of a release cycle. CI happens continuously. The difference is the feedback loop and the state of the main branch.

Remember: Continuous Integration is the practice of merging frequently and verifying automatically. It solves the integration hell problem by making integration a daily activity instead of a quarterly event. The feedback loop is minutes, not weeks. The main branch is always in a known-good state. The artifact is built once and deployed everywhere. The tools support the practice, but the practice is the point.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!