DEV Community

Goodluck Ekeoma Adiole
Goodluck Ekeoma Adiole

Posted on

Deploying a FastAPI Application on AWS EC2 with CI/CD Using GitHub Actions and Nginx

Introduction

In this blog post, we will walk through the process of deploying a FastAPI application on an AWS EC2 instance with Nginx as a reverse proxy, while leveraging GitHub Actions for Continuous Integration and Continuous Deployment (CI/CD). This will ensure that every change pushed to the main branch is automatically deployed to the server.

By the end of this tutorial, you will have a fully functional, continuously deployed FastAPI application running on AWS.

Project Overview

We will be working with a FastAPI Book API and following these key steps:

  1. Implement the missing GET /api/v1/books/{book_id} endpoint.
  2. Test the application locally.
  3. Dockerize the application.
  4. Deploy it on an AWS EC2 instance.
  5. Set up Nginx as a reverse proxy.
  6. Configure GitHub Actions to automate deployment.

Step 1: Implement the Missing Endpoint

The application already provides endpoints for creating, updating, and deleting books, but we need to add an endpoint to retrieve a book by its ID.

Modify api/routes/books.py by adding the missing endpoint:

from typing import OrderedDict
from typing import Dict

from fastapi import APIRouter, status, HTTPException
from fastapi.responses import JSONResponse

from api.db.schemas import Book, Genre, InMemoryDB

router = APIRouter()

db = InMemoryDB()
db.books = {
    1: Book(
        id=1,
        title="The Hobbit",
        author="J.R.R. Tolkien",
        publication_year=1937,
        genre=Genre.SCI_FI,
    ),
    2: Book(
        id=2,
        title="The Lord of the Rings",
        author="J.R.R. Tolkien",
        publication_year=1954,
        genre=Genre.FANTASY,
    ),
    3: Book(
        id=3,
        title="The Return of the King",
        author="J.R.R. Tolkien",
        publication_year=1955,
        genre=Genre.FANTASY,
    ),
}

@router.post("/", status_code=status.HTTP_201_CREATED)
async def create_book(book: Book):
    db.add_book(book)
    return JSONResponse(
        status_code=status.HTTP_201_CREATED, content=book.model_dump()
    )

@router.get("/", response_model=OrderedDict[int, Book], status_code=status.HTTP_200_OK)
async def get_books() -> OrderedDict[int, Book]:
    return db.get_books()

@router.put("/{book_id}", response_model=Book, status_code=status.HTTP_200_OK)
async def update_book(book_id: int, book: Book) -> Book:
    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content=db.update_book(book_id, book).model_dump(),
    )

@router.delete("/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_book(book_id: int) -> None:
    db.delete_book(book_id)
    return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)

# Added the missing api endpoint
@router.get("/{book_id}", response_model=Book, status_code=status.HTTP_200_OK)
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

This ensures that a book is retrieved by its ID or returns a 404 Not Found error if it doesn't exist.


Step 2: Test the Application Locally

Before deploying, test the API locally using pytest:

pytest
Enter fullscreen mode Exit fullscreen mode

To run the FastAPI application locally:

uvicorn api.main:app --host 0.0.0.0 --port 8084 --reload
Enter fullscreen mode Exit fullscreen mode

Test the new endpoint with:

curl -X 'GET' 'http://127.0.0.1:8084/api/v1/books/1' -H 'accept: application/json'
Enter fullscreen mode Exit fullscreen mode

Step 3: Dockerizing the Application

Create a Dockerfile in the root directory:

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8084"]
Enter fullscreen mode Exit fullscreen mode

Build and run the Docker container:

docker build -t fastapi-app .
docker run -d -p 8084:8084 --name fastapi-container fastapi-app
Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy to AWS EC2

1. Launch an EC2 Instance

  • Choose Ubuntu 22.04.
  • Configure security groups to allow SSH (22), HTTP (80), and Custom TCP (8084).

2. SSH into the Instance

ssh -i your-key.pem ubuntu@your-ec2-public-ip
Enter fullscreen mode Exit fullscreen mode

3. Install Docker and Git

sudo apt update && sudo apt install -y docker.io git
Enter fullscreen mode Exit fullscreen mode

4. Clone the Repository and Run the Application

git clone https://github.com/yourusername/fastapi-book-project.git
cd fastapi-book-project
docker build -t fastapi-app .
docker run -d -p 8084:8084 --name fastapi-container fastapi-app
Enter fullscreen mode Exit fullscreen mode

Test the application from your browser: http://your-ec2-public-ip:8084/api/v1/books/1


Step 5: Set Up Nginx as a Reverse Proxy

Install Nginx:

sudo apt install -y nginx
Enter fullscreen mode Exit fullscreen mode

Modify the Nginx configuration file /etc/nginx/sites-available/default:

server {
    listen 80;
    server_name your-ec2-public-ip;

    location / {
        proxy_pass http://127.0.0.1:8084/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
Enter fullscreen mode Exit fullscreen mode

Restart Nginx:

sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

Now, your FastAPI application is accessible at http://your-ec2-public-ip/.


Step 6: Automate Deployment with GitHub Actions

Create .github/workflows/deploy.yml:

name: Deploy FastAPI to EC2

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Deploy to EC2
        uses: appleboy/ssh-action@v0.1.6
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            cd ~/fastapi-book-project
            git pull origin main
            docker build -t fastapi-app .
            docker stop fastapi-container || true
            docker rm fastapi-container || true
            docker run -d -p 8084:8084 --name fastapi-container fastapi-app
            sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

Set Up GitHub Secrets

In your GitHub repository:

  • Navigate to Settings > Secrets > Actions.
  • Add EC2_HOST (EC2 public IP) and EC2_SSH_KEY (your private key).

Trigger Deployment

Run:

git add .
git commit -m "Test auto-deploy"
git push origin main
Enter fullscreen mode Exit fullscreen mode

On a successful push to main, GitHub Actions will automatically deploy the app!


Challenges and Resolutions

1. Nginx Not Proxying Requests

Check Nginx logs:

sudo journalctl -u nginx --no-pager | tail -n 20
Enter fullscreen mode Exit fullscreen mode

2. Docker Container Fails to Start

Verify logs:

docker logs fastapi-container
Enter fullscreen mode Exit fullscreen mode

3. GitHub Actions SSH Key Issues

Ensure EC2_SSH_KEY secret is correctly set in GitHub.


Further Enhancements

  • Use AWS ECS instead of EC2 for better scalability.
  • Set up HTTPS with Let's Encrypt.
  • Store secrets securely using AWS Secrets Manager.

With this setup, you have a robust CI/CD pipeline that ensures seamless deployment of your FastAPI application. 🚀

Top comments (0)