DEV Community

Cover image for Deploying a FastAPI App with CI/CD: GitHub Actions, Docker, Nginx & AWS EC2
Tony Uketui
Tony Uketui

Posted on

Deploying a FastAPI App with CI/CD: GitHub Actions, Docker, Nginx & AWS EC2

Tired of manually deploying your applications?

What if you could push code to GitHub, run tests automatically, and deploy updates to your live server—without lifting a finger?

That’s exactly what I did with my FastAPI project using CI/CD.

In this guide...

I’ll walk you through how I set up a Continuous Integration (CI) pipeline to automatically test my code...

And...

Continuous Deployment (CD) pipeline to push updates to an AWS EC2 instance.

You'll learn how to:

  • Fork the repository and set up FastAPI locally
  • Create a GitHub Actions CI pipeline for automated testing
  • Automate deployment with Docker, Nginx, and EC2

By the end, you’ll have a fully automated deployment workflow that keeps your FastAPI app up-to-date with zero manual effort. Let’s dive in! 🚀

Let's quickly go over how we would set everything

Overview of the Workflow

In this guide, we’ll go through the end-to-end process of deploying a FastAPI application using CI/CD with GitHub Actions, Docker, Nginx, and AWS EC2. Here’s a high-level breakdown of what we’ll cover:

  • Setting up the project – Forking the repo, setting up a virtual environment, and implementing the missing endpoint.

  • Testing locally – Running tests using pytest to ensure our changes work before deployment.

  • Setting up CI (Continuous Integration) – Creating a GitHub Actions pipeline to automatically test our code on every pull request.

  • Preparing for deployment – Adding necessary configuration files like nginx.conf, Dockerfile, and docker-compose.yml.

  • Deploying to AWS EC2 – Creating an EC2 instance, installing dependencies, and running our FastAPI app inside Docker.

  • Setting up CD (Continuous Deployment) – Automating the deployment process so that every merge to the main branch updates the live server.

1. Forked the Repo and Set It Up Locally

I forked the FastAPI Book Project Template repo and added my remote repo so I could track my changes.

Image description

git clone https://github.com/my-username/fastapi-book-project.git
cd fastapi-book-project
Enter fullscreen mode Exit fullscreen mode

Then, I created a virtual environment and installed the dependencies:

python -m venv venv
source venv/bin/activate  # (Windows: venv\Scripts\activate)
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Image description

venv is an indicator that I'm in the virtual environment

2. Added the Missing Endpoint

I created the missing endpoint in books.py to fetch books by ID:

@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

I made sure to import HTTPException. if not it wouldn't work

Image description

After that...

Then, I ran tests locally using pytest to confirm it worked:

pytest
Enter fullscreen mode Exit fullscreen mode

Image description


3. Created the CI Pipeline

I created a .github/workflows/ci.yml file to automate testing.

name: CI Pipeline

on:
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v2

      - name: Set up Python
        uses: actions/setup-python@v3
        with:
          python-version: '3.9'

      - name: Install dependencies
        run: |
          python -m venv venv
          source venv/bin/activate
          pip install -r requirements.txt

      - name: Run tests
        run: |
          source venv/bin/activate
          pytest
Enter fullscreen mode Exit fullscreen mode

I created a feature branch and pushed my changes:

git checkout -b feature-branch
git add .
git commit -m "Added missing endpoint and CI pipeline"
git push origin feature-branch
Enter fullscreen mode Exit fullscreen mode

Then, I created a pull request. The CI pipeline ran and passed, so I merged it.

Image description

Image description


it's this way on my end because I've previously created a pull request and merged it.

4. Deployment Setup - Tested Locally

I first tested everything locally before deploying.

Added Required Files:

  • 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
  • Dockerfile:
  # Use an official lightweight Python image
FROM python:3.9-slim

# Set the working directory
WORKDIR /app

# Copy only the necessary files
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application
COPY . .

# Expose the required port
EXPOSE 8000

# Start FastAPI application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Enter fullscreen mode Exit fullscreen mode
  • 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

I built and ran it locally:

docker-compose up --build -d
Enter fullscreen mode Exit fullscreen mode

Image description

Checked running containers:

docker ps
Enter fullscreen mode Exit fullscreen mode

Image description

Tested it using my local IP in my browser:

Image description

It worked, so I pushed my changes.

git add .
git commit -m "Added Docker and Nginx setup"
git push origin main
Enter fullscreen mode Exit fullscreen mode

5. Deployed to AWS EC2

I created an Ubuntu 22.04 EC2 instance, allowing inbound rules for ports 80 (HTTP) and 22 (SSH).

Image description

SSH into EC2 and Install Docker:

ssh -i my-key.pem ubuntu@my-ec2-ip
sudo apt update && sudo apt install -y docker.io docker-compose
Enter fullscreen mode Exit fullscreen mode

Verify if it(docker and docker-compose) has been installed.

Image description

Image description

Cloned My Repo into EC2:

git clone https://github.com/my-username/fastapi-book-project.git
cd fastapi-book-project
Enter fullscreen mode Exit fullscreen mode

Image description

Ran Docker on EC2:

sudo docker-compose up --build -d
Enter fullscreen mode Exit fullscreen mode

Image description

Checked running containers:

sudo docker ps
Enter fullscreen mode Exit fullscreen mode

Image description

Tested it on EC2:

I copied my EC2 public IP and tested it in a browser:

Image description

The app was running fine.


6. Automated Deployment with CD Pipeline

I automated deployment by creating a .github/workflows/cd.yml file.

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.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_SSH_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.EC2_USER}}

            cd /home/ubuntu/fastapi-book-project
            git fetch --all
            git reset --hard origin/main
            git pull origin main

            # Restart the Docker container
            docker-compose down
            docker-compose up -d --build



Enter fullscreen mode Exit fullscreen mode

I added GitHub Secrets:

  • EC2_HOST → EC2 Public IP
  • EC2_USER → EC2 OS
  • EC2_PRIVATE_KEY → My SSH Key

To add Github Secrets go to the settings>>Secrets&Variables>>Actions

Image description

Then I committed and pushed the changes:

git add .
git commit -m "Added CD pipeline"
git push origin main
Enter fullscreen mode Exit fullscreen mode

Now, every time I push changes and create a pull request:

  • CI pipeline runs tests
  • After merging, CD pipeline deploys the app to EC2 automatically

Image description


Final Workflow Summary

  1. Forked the repo and cloned it locally
  2. Added missing endpoints, and tested locally
  3. Created CI pipeline to automate tests
  4. Added Nginx, Docker, and Docker Compose and tested locally
  5. Deployed to AWS EC2 and confirmed it worked
  6. Created CD pipeline to automate future deployments

🚀 Now, any time I push new changes, my app gets automatically tested and deployed to EC2!

Before we go...

Here are some challenges I faced.

Challenges Faced

  • GitHub Actions Configuration Issues – I initially had syntax errors in my CI/CD YAML files, which caused the workflows to fail.

  • Pull Request Comparison Issue – I mistakenly compared the main branch of the forked repo with my feature branch, instead of comparing my own main branch with the feature branch.

  • Docker installation on my Ec2 Server- The initial commands flagged an error so I had to take another path if you noticed I added this in my cd pipeline

  • Unsuccessful Deployment of latest commits- I Had to use the git fetch command so my directory recognizes commits/changes that will be made. You can see it in my cd pipeline yaml file above.

  • Virtual environment & Dependency Issues

Top comments (3)

Collapse
 
dipe_ profile image
Precious Ogundipe

great one

Collapse
 
tony_uketui_6cca68c7eba02 profile image
Tony Uketui

Thanks Boss🙌

Collapse
 
goku_vegeta_aa8ff000544b0 profile image
Goku Vegeta

Absolutely informative post.