DEV Community

Nauman Tanwir
Nauman Tanwir

Posted on

Deploying FastAPI to AWS: Part 1 - The EC2 Approach (Learning the Fundamentals)

This is Part 1 of a 3-part series on deploying FastAPI applications to AWS. We'll start with EC2 to understand the fundamentals.


I just finished building my first real FastAPI application - a journal API with JWT authentication, PostgreSQL database, and all the features you'd expect. The app worked perfectly on my laptop, but then I hit the wall that every developer faces: how do I deploy this thing?

After trying different approaches and making plenty of mistakes, I decided to document what I learned. This series will cover three main ways to deploy FastAPI to AWS, starting with the most educational approach: EC2.

Why Start with EC2?

I know what you're thinking - "Isn't EC2 old school? Shouldn't I use containers or serverless?"

Here's the thing: starting with EC2 taught me networking, security, and infrastructure concepts that I use every day now. It's like learning to drive with a manual transmission - once you understand the fundamentals, everything else makes more sense.

What We're Building

Our FastAPI application has:

  • User authentication with JWT tokens
  • PostgreSQL database
  • Email functionality for password resets
  • RESTful endpoints for journal entries
  • Environment-based configuration

The goal is to deploy this securely on AWS with proper networking and database separation.

Architecture Overview

We'll create:

Internet Gateway → Public Subnet (FastAPI VM) → Private Subnet (PostgreSQL VM)
Enter fullscreen mode Exit fullscreen mode

This setup teaches you:

  • VPC networking concepts
  • Security group configuration
  • Database security best practices
  • Server management fundamentals

Step 1: Setting Up the Network Infrastructure

Create the VPC

First, let's create our Virtual Private Cloud:

# Create VPC
aws ec2 create-vpc \
    --cidr-block 10.0.0.0/16 \
    --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=journal-api-vpc}]'
Enter fullscreen mode Exit fullscreen mode

Create Subnets

We need a public subnet for our API server and a private subnet for the database:

# Public subnet for API server
aws ec2 create-subnet \
    --vpc-id vpc-xxxxxxxxx \
    --cidr-block 10.0.1.0/24 \
    --availability-zone us-east-1a \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-subnet}]'

# Private subnet for database
aws ec2 create-subnet \
    --vpc-id vpc-xxxxxxxxx \
    --cidr-block 10.0.2.0/24 \
    --availability-zone us-east-1a \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-subnet}]'
Enter fullscreen mode Exit fullscreen mode

Internet Gateway and Routing

# Create and attach an internet gateway
aws ec2 create-internet-gateway \
    --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=journal-igw}]'

aws ec2 attach-internet-gateway \
    --vpc-id vpc-xxxxxxxxx \
    --internet-gateway-id igw-xxxxxxxxx
Enter fullscreen mode Exit fullscreen mode

Step 2: Security Groups (This Is Important!)

Security groups are like firewalls for your instances. Getting this right is crucial for security.

API Server Security Group

# Create a security group for the API server
aws ec2 create-security-group \
    --group-name journal-api-sg \
    --description "Security group for FastAPI server" \
    --vpc-id vpc-xxxxxxxxx

# Allow HTTP traffic on port 8000
aws ec2 authorize-security-group-ingress \
    --group-id sg-xxxxxxxxx \
    --protocol tcp \
    --port 8000 \
    --cidr 0.0.0.0/0

# Allow SSH for management
aws ec2 authorize-security-group-ingress \
    --group-id sg-xxxxxxxxx \
    --protocol tcp \
    --port 22 \
    --cidr 0.0.0.0/0
Enter fullscreen mode Exit fullscreen mode

Database Security Group

# Create a security group for the database
aws ec2 create-security-group \
    --group-name journal-db-sg \
    --description "Security group for PostgreSQL" \
    --vpc-id vpc-xxxxxxxxx

# Allow PostgreSQL access ONLY from the API server
aws ec2 authorize-security-group-ingress \
    --group-id sg-yyyyyyyyy \
    --protocol tcp \
    --port 5432 \
    --source-group sg-xxxxxxxxx
Enter fullscreen mode Exit fullscreen mode

Key Point: Notice how the database only accepts connections from the API server security group, not from the entire internet. This is a fundamental security practice.

Step 3: Launch the Instances

API Server Instance

aws ec2 run-instances \
    --image-id ami-0c02fb55956c7d316 \
    --instance-type t3.micro \
    --key-name your-key-pair \
    --security-group-ids sg-xxxxxxxxx \
    --subnet-id subnet-xxxxxxxxx \
    --associate-public-ip-address \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=journal-api-server}]'
Enter fullscreen mode Exit fullscreen mode

Database Instance

aws ec2 run-instances \
    --image-id ami-0c02fb55956c7d316 \
    --instance-type t3.micro \
    --key-name your-key-pair \
    --security-group-ids sg-yyyyyyyyy \
    --subnet-id subnet-yyyyyyyyy \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=journal-db-server}]'
Enter fullscreen mode Exit fullscreen mode

Step 4: Setting Up the API Server

SSH into your API server and let's get FastAPI running:

# Update the system
sudo apt update && sudo apt upgrade -y

# Install Python 3.11
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
sudo apt install python3.11 python3.11-venv python3.11-dev python3-pip git -y

# Clone your repository (replace with your repo)
git clone https://github.com/your-username/journal-api.git
cd journal-api

# Create virtual environment
python3.11 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Environment Configuration

Create your .env file with the database connection:

cat > .env << EOF
POSTGRES_HOST=10.0.2.xxx  # Private IP of your database server
POSTGRES_PORT=5432
POSTGRES_USER=journal_user
POSTGRES_PASSWORD=your_secure_password
POSTGRES_DB=journal_db
SECRET_KEY=your_jwt_secret_key
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7
EMAIL_SENDER=your_email@example.com
EMAIL_PASSWORD=your_app_password
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EOF
Enter fullscreen mode Exit fullscreen mode

Step 5: Database Setup

Now let's set up PostgreSQL on the database server. You'll need to access it through a bastion host or AWS Systems Manager Session Manager since it's in a private subnet.

# Install PostgreSQL
sudo apt update
sudo apt install postgresql postgresql-contrib -y

# Start and enable PostgreSQL
sudo systemctl start postgresql
sudo systemctl enable postgresql

# Create a database and a user
sudo -u postgres psql
Enter fullscreen mode Exit fullscreen mode

In the PostgreSQL prompt:

CREATE DATABASE journal_db;
CREATE USER journal_user WITH PASSWORD 'your_secure_password';
GRANT ALL PRIVILEGES ON DATABASE journal_db TO journal_user;
\q
Enter fullscreen mode Exit fullscreen mode

Configure PostgreSQL for Remote Access

Edit the configuration files:

# Edit postgresql.conf
sudo nano /etc/postgresql/15/main/postgresql.conf
# Change: listen_addresses = '*'

# Edit pg_hba.conf
sudo nano /etc/postgresql/15/main/pg_hba.conf
# Add: host    all    all    10.0.1.0/24    md5

# Restart PostgreSQL
sudo systemctl restart postgresql
Enter fullscreen mode Exit fullscreen mode

Step 6: Deploy and Test

Back on your API server, run the database migrations:

cd journal-api
source venv/bin/activate
alembic upgrade head
Enter fullscreen mode Exit fullscreen mode

Start your FastAPI application:

uvicorn app.main:app --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode

Test it:

curl http://your-public-ip:8000/docs
Enter fullscreen mode Exit fullscreen mode

Making It Production Ready

For a production setup, create a systemd service:

sudo tee /etc/systemd/system/journal-api.service > /dev/null <<EOF
[Unit]
Description=Journal API FastAPI application
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/journal-api
Environment=PATH=/home/ubuntu/journal-api/venv/bin
ExecStart=/home/ubuntu/journal-api/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=always

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable journal-api
sudo systemctl start journal-api
Enter fullscreen mode Exit fullscreen mode

What I Learned

The Good:

  • Complete control over the environment
  • Great for understanding networking and security
  • Predictable costs
  • Easy to debug with direct SSH access

The Challenges:

  • Manual scaling (you handle load balancing yourself)
  • Ongoing maintenance (OS updates, security patches)
  • Single points of failure
  • More operational overhead

Cost: Around $25-50/month for a small application

Common Mistakes to Avoid

  1. Opening security groups too wide - Only allow the minimum required access
  2. Not setting up monitoring - At least enable CloudWatch basic monitoring
  3. Forgetting backups - Set up automated database backups
  4. Not using Elastic IP - Your public IP will change when you restart the instance

What's Next?

In Part 2, we'll look at containerizing this same application and deploying it with ECS Fargate. You'll see how containers solve many of the operational challenges we faced with EC2.

In Part 3, we'll explore the serverless approach with Lambda and see when it makes sense to go completely serverless.


Have you tried deploying FastAPI to EC2? What challenges did you face? Drop your questions in the comments - I'll answer them and might include solutions in the next parts of this series.

Found this helpful? Follow me for Parts 2 and 3 of this series!


Next up: Part 2 - Containerizing with ECS Fargate

fastapi #aws #python #webdev #devops #beginners #tutorial #ec2

Top comments (0)