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.
git clone https://github.com/my-username/fastapi-book-project.git
cd fastapi-book-project
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
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
I made sure to import HTTPException. if not it wouldn't work
After that...
Then, I ran tests locally using pytest to confirm it worked:
pytest
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
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
Then, I created a pull request. The CI pipeline ran and passed, so I merged it.
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;
}
}
-
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"]
-
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
I built and ran it locally:
docker-compose up --build -d
Checked running containers:
docker ps
Tested it using my local IP in my browser:
It worked, so I pushed my changes.
git add .
git commit -m "Added Docker and Nginx setup"
git push origin main
5. Deployed to AWS EC2
I created an Ubuntu 22.04 EC2 instance, allowing inbound rules for ports 80 (HTTP) and 22 (SSH).
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
Verify if it(docker and docker-compose) has been installed.
Cloned My Repo into EC2:
git clone https://github.com/my-username/fastapi-book-project.git
cd fastapi-book-project
Ran Docker on EC2:
sudo docker-compose up --build -d
Checked running containers:
sudo docker ps
Tested it on EC2:
I copied my EC2 public IP and tested it in a browser:
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
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
Then I committed and pushed the changes:
git add .
git commit -m "Added CD pipeline"
git push origin main
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
Final Workflow Summary
- Forked the repo and cloned it locally
- Added missing endpoints, and tested locally
- Created CI pipeline to automate tests
- Added Nginx, Docker, and Docker Compose and tested locally
- Deployed to AWS EC2 and confirmed it worked
- 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)
great one
Thanks Boss🙌
Absolutely informative post.