KAHIBARO
Discord Login Register

24.2. Continuous Integration

Why Continuous Integration Matters

Continuous Integration, often shortened to CI, is a practice where developers frequently merge their code changes into a shared repository and automatically verify that the changes do not break the project.

Instead of waiting days or weeks to integrate features, CI encourages you to integrate many small changes multiple times per day. Each integration triggers automated checks, such as running tests and linters.

In a backend context, CI becomes the safety net every time you push code to your API, database layer, background workers, or infrastructure scripts.

Key idea: CI is about frequent code integration combined with automatic checks that run on every change.

A simple timeline comparison helps:

ApproachIntegration FrequencyManual WorkRisk of BreakageFeedback Speed
No CIRare (big batches)HighVery highSlow
Basic CI (few tests)Often (small batches)MediumLowerFaster
Mature CI (full pipeline)Very oftenLowMuch lowerVery fast

Backend projects become easier to maintain when you can trust an automated system to tell you quickly if something is broken.

Core Concepts of Continuous Integration

CI is not a single tool or service. It is a process built around several ideas that work together.

Shared main branch

A typical backend repository has one main integration branch, often called main or master. All new code, after being reviewed and tested, ends up here.

Developers create feature branches like:

They push these branches, open pull requests (or merge requests), and use CI to verify that the branch is safe to merge.

Automated builds

A build step prepares your application for running:

In a Python backend, the build step can be as simple as:

bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

The CI system runs this automatically on every push or pull request so you do not have to do it manually.

Automated tests

Tests give you confidence that existing behavior still works. CI runs them every time you push.

Common test types for a backend:

Test TypeChecksExample
Unit testsIndividual functions or methodsEmail validation function returns correct result
IntegrationComponents working togetherAPI endpoint reads from PostgreSQL correctly
API testsHTTP endpoints and responses/api/orders returns 201 and correct JSON
Smoke testsVery basic health checksService starts and health check endpoint works

Running tests locally is good. Running them in CI is better, because every change is checked in a clean environment that resembles production.

Rule: Every push to a shared branch must trigger tests. If tests fail, do not merge.

Fast, frequent feedback

CI works only if feedback is fast.

For backend projects, aim for:

Single source of truth

The CI pipeline is the trusted definition of how your project is built and verified.

This avoids the situation where:

By encoding commands into CI config files (YAML or similar), you make the process explicit, repeatable, and reviewable.

Typical Continuous Integration Workflow

To understand CI, walk through a concrete developer story on a backend project.

Local work

  1. Pull the latest main branch.
  2. Create a new feature branch, for example feature/add-task-search.
  3. Implement new functionality.
  4. Run tests locally, at least for the parts you touched.
  5. Commit your changes.

Push and create a pull request

You push your branch:

bash
git push origin feature/add-task-search

Then you open a pull request into main on GitHub, GitLab, or another platform.

At that moment, CI typically:

CI pipeline stages

Most CI systems define stages that run in order.

A simple backend CI pipeline might have:

  1. Lint
    • Run static analysis tools, for example flake8, black --check, mypy.
  2. Test
    • Run unit tests and small integration tests, for example pytest.
  3. Build
    • Build a Docker image or other deployment artifact.

Example of a simple logical flow:

StageExample CommandsPurpose
lintpip install -r dev-requirements.txt<br>flake8 .Catch style or simple code issues
testpytest -qEnsure behavior is correct
builddocker build -t my-api:pr-123 .Ensure code is buildable

If any stage fails, the pipeline stops and marks the pull request as failing.

Fix and repeat

If CI fails:

  1. You inspect the logs in the CI interface.
  2. You fix the problem locally.
  3. You commit and push again.
  4. CI re-runs automatically on the new commit.

Once all stages pass and code review is complete, the branch merges into main.

Continuous Integration vs Continuous Delivery vs Continuous Deployment

These three concepts are related but not the same.

ConceptMain FocusAutomation Level
Continuous IntegrationIntegrate and test code frequentlyBuild + tests on every change
Continuous DeliveryKeep code always deployableCI + automatic packaging + manual deploy trigger
Continuous DeploymentDeploy automatically on every successful buildCI + CD + auto deploy to production

In this chapter you focus only on Continuous Integration, that is build and test automation. Deployment automation is handled in separate chapters on Continuous Delivery, Continuous Deployment, and CI/CD pipelines.

What CI Does for Backend Projects

Backend applications often have more moving parts than small frontend apps. CI helps keep these parts consistent and working.

Consistent environments

Instead of each developer having a slightly different environment on their laptop, CI runs:

This reduces the classic line:

"It works on my machine."

If it does not work in CI, you treat that as the truth.

Catching integration bugs early

Even if your code works alone, integration with:

can introduce subtle bugs.

By integrating small changes frequently, you discover integration problems while they are still easy to fix.

Enforcing quality standards

CI can run checks that are easy to forget:

You do not have to remind team members to run these manually. The CI system enforces them consistently.

Simple Examples of CI Configuration

Concrete examples help make this less abstract. Imagine a basic Python/FastAPI backend.

Example: GitHub Actions CI for a Python backend

A minimal .github/workflows/ci.yml:

yaml
name: CI
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - name: Install dependencies
        run: |
          python -m venv .venv
          source .venv/bin/activate
          pip install -U pip
          pip install -r requirements.txt
          pip install -r dev-requirements.txt
      - name: Run linters
        run: |
          source .venv/bin/activate
          flake8 .
          black --check .
          mypy .
      - name: Run tests
        env:
          DATABASE_URL: "postgresql://test:test@localhost:5432/testdb"
        run: |
          source .venv/bin/activate
          pytest -q

Key observations:

You do not need to memorize GitHub Actions syntax in this chapter. Instead, notice how the CI file becomes the script that defines your integration process.

Example: GitLab CI for a backend project

A simple .gitlab-ci.yml:

yaml
stages:
  - lint
  - test
variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
lint:
  stage: lint
  image: python:3.11
  script:
    - pip install -r dev-requirements.txt
    - flake8 .
    - black --check .
    - mypy .
  artifacts:
    when: always
    paths:
      - flake8.log
    expire_in: 1 week
test:
  stage: test
  image: python:3.11
  services:
    - name: postgres:15
      alias: db
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
    DATABASE_URL: "postgresql://test:test@db:5432/testdb"
  script:
    - pip install -r requirements.txt
    - pip install -r dev-requirements.txt
    - pytest -q

Here:

Again, the idea is: define how to integrate and test your backend using configuration, not manual steps.

Good Practices for Continuous Integration

Even a simple CI setup benefits from some basic principles.

Keep pipelines fast

A quick pipeline encourages frequent integration.

Ideas to keep it fast:

For example, caching pip packages:

yaml
      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

Fail fast

Configure CI to fail as soon as a serious problem is detected.

For example:

This saves compute time and gives feedback faster.

Treat warnings seriously

If tests pass but CI shows warnings, do not ignore them.

Warnings can indicate:

Fixing them early keeps your backend more stable.

Run CI on every branch that might be merged

Running CI only on main is too late. A branch could break the build and block everyone.

Configure CI to run on:

This makes sure you never merge unverified code.

Rule: Do not merge a pull request if the CI status is failing or missing.

Simple CI Checklist for Beginners

When you start a new backend project, you can use this short list.

StepQuestion to Ask
Define main branchWhat is the integration branch (for example main)?
Choose a CI platformGitHub Actions, GitLab CI, or others?
Trigger conditionsShould CI run on push, pull request, or both?
Install dependenciesHow will Python and packages be installed?
Run testsWhat pytest command should CI run?
Run quality checksWhich linters and formatters will you use?
Pipeline speedDoes it finish in a few minutes?

If you can answer these questions, you have the basics of Continuous Integration in place for your backend project.

Views: 9

Comments

Please login to add a comment.

Don't have an account? Register now!