Categories
CI/CD Dev Docker GitHub Python

Building a Complete CI/CD Pipeline: A Step-by-Step Tutorial

In modern software development, Continuous Integration and Continuous Deployment (CI/CD) have become essential practices for delivering high-quality applications efficiently. CI/CD pipelines automate the process of integrating code changes, testing, and deploying applications, reducing errors and accelerating the development cycle.

This tutorial will guide you through building a robust CI/CD pipeline using FastAPI and GitHub Actions. We will explore each stage of the pipeline, best practices, security considerations, and troubleshooting tips to ensure a seamless workflow.

Understanding CI/CD and Its Importance

Before diving into the implementation, let’s briefly understand what CI/CD is and why it’s crucial:

  • Continuous Integration (CI): The practice of frequently integrating code changes into a shared repository, automatically building, and testing them to catch issues early.
  • Continuous Deployment (CD): The process of automatically deploying tested code to production, ensuring quick and reliable software releases.

Benefits of CI/CD include:

  • Faster software delivery
  • Improved code quality and security
  • Reduced manual intervention and deployment risks
  • Enhanced collaboration among development teams

Overview of the CI/CD Pipeline Stages

A complete CI/CD pipeline consists of multiple stages that automate the software delivery process:

1. Development and Code Commit

  • Follow a clear branching strategy (e.g., Gitflow) to manage feature development, bug fixes, and releases efficiently.
  • Use meaningful commit messages and pull requests to document changes.
  • Enforce coding standards with linting tools such as Flake8 for Python.

2. Build Stage

  • Automate the build process to ensure consistency across different environments.
  • Utilise containerisation tools like Docker to package applications with their dependencies.
  • Implement dependency management to avoid conflicts.

3. Testing Stage

  • Integrate automated testing into the pipeline:
    • Unit Tests: Verify individual components using pytest.
    • Integration Tests: Ensure different components work together correctly.
    • Security Scans: Use tools like Bandit to check for vulnerabilities.
  • Generate test reports for better debugging and visibility.

4. Deployment Stage

  • Use GitHub Actions to automate deployments to various environments (staging, production).
  • Implement deployment strategies such as blue-green deployments or canary releases to minimise downtime and risk.
  • Set up rollback mechanisms in case of failed deployments.

Prerequisites

Before we begin, ensure you have:

  • Python 3.11 installed
  • Git installed
  • A GitHub account
  • Basic knowledge of Python and REST APIs

Project Overview

We’ll build a simple calculator API with these features:

  • Basic arithmetic operations (add, subtract, multiply, divide)
  • REST API endpoints
  • Automated testing
  • Docker containerisation
  • CI/CD pipeline with GitHub Actions

Step 1: Setting Up Your Development Environment

First, let’s set up a proper Python development environment.

Installing Python 3.11# macOS (using Homebrew)
brew install python@3.11
# Ubuntu/Debian
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.11 python3.11-venv

Creating the Project Structure

# Create project directory
mkdir simple-cicd-demo
cd simple-cicd-demo

# Create virtual environment
python3.11 -m venv venv

# Activate virtual environment
source venv/bin/activate # On Unix/macOS
# or
venv\Scripts\activate # On Windows

# Create project structure
mkdir -p src tests .github/workflows

Step 2: Setting Up the Project Files

1. Create requirements.txt

[code language="text"]
fastapi==0.95.2
uvicorn==0.22.0
pytest==7.0.0
requests==2.26.0
pydantic==1.10.7

2. Install Dependencies

pip install -r requirements.txt

3. Create Calculator Logic (src/calculator.py)

class Calculator:
@staticmethod
def add(x: float, y: float) -> float:
return x + y

@staticmethod
def subtract(x: float, y: float) -> float:
return x - y

@staticmethod
def multiply(x: float, y: float) -> float:
return x * y

@staticmethod
def divide(x: float, y: float) -> float:
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y

4. Create FastAPI Application (src/main.py)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from .calculator import Calculator

app = FastAPI()
calculator = Calculator()

class CalculationRequest(BaseModel):
x: float
y: float

@app.post("/add")
def add(request: CalculationRequest):
return {"result": calculator.add(request.x, request.y)}

@app.post("/subtract")
def subtract(request: CalculationRequest):
return {"result": calculator.subtract(request.x, request.y)}

@app.post("/multiply")
def multiply(request: CalculationRequest):
return {"result": calculator.multiply(request.x, request.y)}

@app.post("/divide")
def divide(request: CalculationRequest):
try:
return {"result": calculator.divide(request.x, request.y)}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))

5. Create Tests (tests/test_calculator.py)

import pytest
from src.calculator import Calculator

def test_add():
assert Calculator.add(2, 3) == 5
assert Calculator.add(-1, 1) == 0

def test_subtract():
assert Calculator.subtract(5, 3) == 2
assert Calculator.subtract(1, 1) == 0

def test_multiply():
assert Calculator.multiply(2, 3) == 6
assert Calculator.multiply(-2, 3) == -6

def test_divide():
assert Calculator.divide(6, 2) == 3
assert Calculator.divide(5, 2) == 2.5

def test_divide_by_zero():
with pytest.raises(ValueError):
Calculator.divide(5, 0)

Step 3: Setting Up CI/CD

1. Create Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY src/ src/

CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

2. Create GitHub Actions Workflow (.github/workflows/main.yml)

name: CI/CD Pipeline

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run tests
run: |
pytest tests/

build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v2

- name: Build Docker image
run: docker build -t calculator-api .

Step 4: Running the Application

Local Development

# Make sure virtual environment is activated
source venv/bin/activate

# Run tests
pytest tests/

# Start the application
uvicorn src.main:app --reload

Testing the API

Once the application is running, you can test it using curl:

curl -X POST -H "Content-Type: application/json" -d'{"x": 10, "y": 5}' http://localhost:8000/add
Expected response:
[code language="json"]
{"result": 15}

Step 5: Setting Up GitHub Repository

1. Create a new repository on GitHub
2. Initialise and push your code:

git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin 
git push -u origin main

Understanding the CI/CD Pipeline

Our pipeline performs these steps automatically:

  1. Continuous Integration
    • Runs on every push and pull request
    • Executes all tests
    • Ensures code quality
  2. Continuous Deployment
    • Builds Docker image
    • Ready for deployment to any cloud platform

Troubleshooting Common Issues

Import Errors

If you see import errors, ensure:

  • Virtual environment is activated
  • All dependencies are installed
  • You’re running commands from the project root

Version Conflicts

If you encounter version conflicts:

  • Delete the virtual environment
  • Recreate it using the steps above
  • Install dependencies fresh from requirements.txt

Next Steps

To extend this project, consider:

  • Adding more test cases
  • Implementing code coverage reporting
  • Adding API documentation using Swagger
  • Setting up automatic deployment to a cloud platform
  • Adding branch protection rules in GitHub

Conclusion

You now have a working CI/CD pipeline with:

  • Automated testing
  • Docker containerisation
  • GitHub Actions integration
  • A solid foundation for further development

This setup provides a great starting point for learning CI/CD concepts and can be extended for more complex applications.

Leave a Reply

Your email address will not be published. Required fields are marked *