Deploying a FastAPI Application with CI/CD Pipeline: HNG Task 2

B.G.Skillz 🧑‍💻 - Feb 15 - - Dev Community

Introduction
In this article, I will explain how I deployed a FASTAPI application with continuous Integration (CI) and Continuous deployment (CD).

Tools Requirements

  • Python
  • Docker
  • GitHub Actions Secrets e.g SSH_HOST, SSH_PRIVATE_KEY
  • AWS Linux Server
  • Nginx
  • FastAPI Application

First Step: Fork and Clone the Repository
The first step is to fock the repo and clone the repository to your local device
git clone https:/github.com/hng12-devbotops/fastapi-book-project.git
Then you navigate to your repo after clone it
cd fastapi-book-project/

Second Step: Setting Up a Virtual Environment
Testing the application locally before deploying, using a virtual environment is good for Python dependencies. And to achieve this you need to create and and activate a virtual environment.
python3 -m venv hng
source hng/bin/activate
And connecting to virtual environment I then install the required dependency using the following command.
pip install -r requirements.txt

Third Step: Implementing the Missing Endpoing in book.py
The API is currently unable to fetch book using its ID. In this step, I added a new endpoing to the FASTAPI router that handles a GET request to retrieve a book based on provided ID.
So I updated the missing endpoint by add this code to book.py file

@router.get("/{book_id}", response_model=Book, status_code=status.HTTP_200_OK)  # New endpoint
async def get_book(book_id: int):
    book = db.books.get(book_id)
    if not book:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, 
            detail="Book not found"
        )
    return book
Enter fullscreen mode Exit fullscreen mode

And also the HTTPException was imported from fastapi

Forth Step: Testing the Endpoint to check if it working perfectly
Once I finish implementing the endpoint, it's important to test it to ensure it working perfectly as expected, So I confirm using pytest commmand

Image description

After this I start the FASTAPI application using Uvicorn uvicorn main:app --reload

{
  "1": {
    "id": 1,
    "title": "The Hobbit",
    "author": "J.R.R. Tolkien",
    "publication_year": 1937,
    "genre": "Science Fiction"
  },
  "2": {
    "id": 2,
    "title": "The Lord of the Rings",
    "author": "J.R.R. Tolkien",
    "publication_year": 1954,
    "genre": "Fantasy"
  },
  "3": {
    "id": 3,
    "title": "The Return of the King",
    "author": "J.R.R. Tolkien",
    "publication_year": 1955,
    "genre": "Fantasy"
  }
}
Enter fullscreen mode Exit fullscreen mode

The application seem to be running fine on the localhost

Fifth Test: Dockerizing the APP
create a Dockerfile in the project directory

# Use an official lightweight Python image
FROM python:3.9-slim

# Set work directory
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

# Copy project files
COPY . .

# Expose the port FastAPI runs on
EXPOSE 8000

# Run the FastAPI application using Uvicorn
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Enter fullscreen mode Exit fullscreen mode

Sixth Step: Nginx Integrating
To make things easier for me, I used a docker-compose.yml file to configure Nginx as a reverse proxy. To do this I created an nginx.conf file with the configurations for my application. The purpose of this configuration is to have Nginx listen on port 80 and forward incoming HTTP requests to the FastAPI application running on port 8000 inside its container.
nginx.conf

server {
    listen 80;
    server_name your_domain_or_IP;
    location / {
        proxy_pass http://app:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For 
        $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then processed to the docker-compose.yml

version: '3.8'
services:
  app:
    build: .
    container_name: fastapi-app
    ports:
      - "8000:8000"

  nginx:
    image: nginx:latest
    container_name: fastapi-nginx
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - "80:80"
    depends_on:
      - app
Enter fullscreen mode Exit fullscreen mode
  • The app service builds and runs our FastAPI application.
  • The Nginx service uses an Nginx image, mounts the custom configuration file, and maps port 80 of the container to port 80 on the host.
  • The depends_on command ensures that the FastAPI container starts before Nginx.
  • Then I used this command to build the containers docker-compose up --build

Image description

Seventh Step: Creating a Linux Server
For hosting and deploying our FastAPI application, I set up a Linux server on AWS. This server serves as the backbone of our deployment environment, where our Dockerized application will run and be accessible to users.

Eighth Step: Set Up the CI/CD Pipeline
I created a GitHub Actions Workflow File for this step to automatically run tests on every pull request targeting the main branch. This ensures that any new changes are validated before they can be merged.
mkdir -p .github/workflows/
cd .github/workflows/
nano test.yml
After the necessary details needed has been input and I close using Ctrl X then press Y and Enter

name: ci pipeline

on:
  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.9'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run tests
      run: pytest
Enter fullscreen mode Exit fullscreen mode

Then follow by the CD Pipeline/Deployment Pipeline
For continuous deployment, I created another GitHub Actions Workflow File named deploy.yml. This deployment pipeline is triggered whenever changes are pushed to the main branch, automating the process of updating the live application.
nano deploy.yml

name: CD Pipeline
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            # Update package index and install dependencies
            sudo apt-get update -y
            sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common

            # Add Docker's official GPG key
            curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
            sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

            # Install Docker
            sudo apt-get update -y
            sudo apt-get install -y docker-ce docker-ce-cli containerd.io

            # Add the SSH user to the Docker group
            sudo usermod -aG docker ${{ secrets.SSH_USERNAME }}

            # Install Docker Compose
            sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
            sudo chmod +x /usr/local/bin/docker-compose

            # Verify installations
            docker --version
            docker-compose --version

            # Navigate to the project directory and deploy
            cd /home/ubuntu/
            git clone <your-github-repo>
            cd fastapi-book-project/
            git pull
            docker-compose up -d --build
Enter fullscreen mode Exit fullscreen mode

After pushing the file git add. deploy.yml test.yml and Check the status of the Pipeline they are working perfectly

Image description

After that the Post-Deployment Verification the pipeline are working perfectly

Image description

Thanks for reading, don't forget to drop a like and comment

LOVE YOU

. . . .