Skip to main content

FastAPI Heroku Deployment

Introduction

Deploying your FastAPI application to Heroku is an excellent way to make your API accessible to users worldwide. Heroku provides a platform-as-a-service (PaaS) solution that simplifies the deployment process, allowing you to focus on your application's functionality rather than infrastructure management.

In this tutorial, we'll walk through the process of deploying a FastAPI application to Heroku, from setting up the necessary configuration files to deploying and maintaining your application.

Prerequisites

Before we begin, make sure you have:

  1. A working FastAPI application
  2. Git installed on your machine
  3. A Heroku account (free to create)
  4. Heroku CLI installed locally

Step 1: Preparing Your FastAPI Application

Project Structure

Let's assume you have a basic FastAPI application with the following structure:

my_fastapi_app/
├── app/
│ ├── __init__.py
│ ├── main.py
│ └── routers/
│ ├── __init__.py
│ └── items.py
├── requirements.txt
└── README.md

Creating Required Heroku Files

For Heroku deployment, we need to add three important files:

  1. Procfile - Tells Heroku how to run your application
  2. runtime.txt - Specifies the Python version
  3. requirements.txt - Lists all your dependencies

1. Create a Procfile

Create a file named Procfile (no file extension) in your project root:

web: uvicorn app.main:app --host=0.0.0.0 --port=${PORT:-5000}

This tells Heroku to run your application using Uvicorn and to use the PORT environment variable that Heroku will provide.

2. Create runtime.txt

Specify the Python version you want to use:

python-3.9.13

3. Update requirements.txt

Make sure your requirements.txt includes all necessary dependencies:

fastapi>=0.68.0
uvicorn>=0.15.0
gunicorn>=20.1.0

Step 2: Adapting Your FastAPI Application for Heroku

Modify main.py

Let's update your main.py file to ensure it works properly on Heroku:

python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

# Import your routers
from app.routers import items

# Create FastAPI instance
app = FastAPI(
title="My FastAPI App",
description="A simple API deployed on Heroku",
version="0.1.0"
)

# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, replace with specific origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Include routers
app.include_router(items.router)

@app.get("/")
async def root():
return {"message": "Welcome to my FastAPI application deployed on Heroku!"}

# For debugging - not needed in production
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)

Step 3: Setting Up Heroku

Login to Heroku CLI

Open your terminal and log in to the Heroku CLI:

bash
heroku login

This will open a browser window where you can log in to your Heroku account.

Create a New Heroku App

Create a new Heroku application with the following command:

bash
heroku create your-app-name

Replace your-app-name with a unique name for your application. If you don't specify a name, Heroku will generate a random one for you.

Step 4: Deploying Your Application

Initialize Git Repository

If your project isn't already a Git repository, initialize one:

bash
git init
git add .
git commit -m "Initial commit for Heroku deployment"

Add Heroku Remote

Add the Heroku remote to your Git repository:

bash
heroku git:remote -a your-app-name

Push to Heroku

Deploy your application by pushing to the Heroku remote:

bash
git push heroku main

If you're using the master branch instead of main, use:

bash
git push heroku master

Heroku will now build and deploy your application. You'll see the build progress in your terminal.

Open Your Application

Once deployment is complete, you can open your application in a browser:

bash
heroku open

Or you can visit https://your-app-name.herokuapp.com directly.

Step 5: Testing Your Deployed API

Using the Swagger UI

FastAPI automatically provides a Swagger UI at the /docs endpoint. Visit:

https://your-app-name.herokuapp.com/docs

Here you can test all your API endpoints interactively.

Example API Request

Let's say you have an endpoint to get a list of items. You can test it using curl:

bash
curl -X 'GET' \
'https://your-app-name.herokuapp.com/items/' \
-H 'accept: application/json'

Expected output:

json
[
{"id": 1, "name": "Item 1", "description": "This is item 1"},
{"id": 2, "name": "Item 2", "description": "This is item 2"}
]

Step 6: Configuring Environment Variables

Setting Environment Variables in Heroku

If your application requires environment variables (e.g., database URLs, API keys), you can set them in Heroku:

bash
heroku config:set DATABASE_URL="postgresql://user:password@host:port/database"
heroku config:set API_KEY="your-secret-key"

Reading Environment Variables in FastAPI

Modify your application to read these environment variables:

python
import os
from fastapi import FastAPI

app = FastAPI()

# Get environment variables
database_url = os.getenv("DATABASE_URL", "sqlite:///./test.db")
api_key = os.getenv("API_KEY", "default-dev-key")

@app.get("/config")
async def get_config():
return {
"database_type": database_url.split(":")[0],
"api_key_configured": bool(api_key != "default-dev-key")
}

Step 7: Scaling Your Application

Checking Dyno Status

Check the current status of your application dynos:

bash
heroku ps

Scaling Up or Down

You can scale your application by adding more dynos (note that this may incur charges):

bash
heroku ps:scale web=2

To scale back down:

bash
heroku ps:scale web=1

Step 8: Monitoring Your Application

Viewing Logs

To view the logs from your application:

bash
heroku logs --tail

Checking Application Metrics

You can monitor your application's performance in the Heroku Dashboard:

  1. Go to https://dashboard.heroku.com/apps
  2. Select your application
  3. Navigate to the "Metrics" tab

Step 9: Common Issues and Troubleshooting

Application Crashes

If your application crashes after deployment, check the logs:

bash
heroku logs --tail

Common issues include:

  • Missing dependencies in requirements.txt
  • Path errors in the Procfile
  • Environment variables not properly configured

H10 - App Crashed Error

This usually indicates that your application can't start. Check your logs for Python errors and make sure:

  • Your main.py file is in the correct location
  • Your app object is correctly named in the Procfile
  • All dependencies are installed

R10 - Boot Timeout

If your application takes too long to start (over 60 seconds), you'll see this error. Solutions include:

  • Optimize your application's startup time
  • Use a worker process for long-running startup tasks

Step 10: Continuous Deployment with GitHub

Connect Heroku to GitHub

For continuous deployment:

  1. Go to your app in the Heroku Dashboard
  2. Go to the "Deploy" tab
  3. Select "GitHub" as the deployment method
  4. Connect to your GitHub repository
  5. Enable automatic deploys from your main branch

Now every time you push to your GitHub repository, Heroku will automatically deploy your application.

Real-world Example: FastAPI Todo API

Let's create a complete todo API example that you can deploy to Heroku:

Project Structure

fastapi-heroku-todo/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ └── database.py
├── Procfile
├── runtime.txt
└── requirements.txt

models.py

python
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime

class TodoBase(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False

class TodoCreate(TodoBase):
pass

class Todo(TodoBase):
id: int
created_at: datetime

class Config:
orm_mode = True

database.py

python
from datetime import datetime

# Simple in-memory database for demonstration
class DBSession:
def __init__(self):
self.todos = {}
self.counter = 0

def get_todos(self):
return list(self.todos.values())

def get_todo(self, todo_id):
return self.todos.get(todo_id)

def create_todo(self, todo):
self.counter += 1
todo_dict = todo.dict()
todo_dict.update({
"id": self.counter,
"created_at": datetime.now()
})
self.todos[self.counter] = todo_dict
return todo_dict

def update_todo(self, todo_id, todo):
if todo_id not in self.todos:
return None
todo_dict = self.todos[todo_id]
update_data = todo.dict(exclude_unset=True)
for field in update_data:
if field != "id":
todo_dict[field] = update_data[field]
return todo_dict

def delete_todo(self, todo_id):
if todo_id not in self.todos:
return False
del self.todos[todo_id]
return True

db = DBSession()

main.py

python
from fastapi import FastAPI, HTTPException, status
from app.models import Todo, TodoCreate, TodoBase
from app.database import db
from typing import List
import os

app = FastAPI(
title="Todo API",
description="A simple Todo API deployed on Heroku",
version="1.0.0"
)

@app.get("/")
async def root():
return {
"message": "Welcome to the Todo API",
"docs": "/docs",
"todos": "/todos"
}

@app.get("/todos", response_model=List[Todo])
async def get_todos():
return db.get_todos()

@app.post("/todos", response_model=Todo, status_code=status.HTTP_201_CREATED)
async def create_todo(todo: TodoCreate):
return db.create_todo(todo)

@app.get("/todos/{todo_id}", response_model=Todo)
async def get_todo(todo_id: int):
todo = db.get_todo(todo_id)
if todo is None:
raise HTTPException(status_code=404, detail="Todo not found")
return todo

@app.put("/todos/{todo_id}", response_model=Todo)
async def update_todo(todo_id: int, todo: TodoBase):
updated_todo = db.update_todo(todo_id, todo)
if updated_todo is None:
raise HTTPException(status_code=404, detail="Todo not found")
return updated_todo

@app.delete("/todos/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(todo_id: int):
success = db.delete_todo(todo_id)
if not success:
raise HTTPException(status_code=404, detail="Todo not found")
return None

requirements.txt

fastapi==0.95.0
uvicorn==0.21.1
gunicorn==20.1.0
pydantic==1.10.7

Procfile

web: uvicorn app.main:app --host=0.0.0.0 --port=${PORT:-5000}

runtime.txt

python-3.9.13

Deployment Steps

  1. Create the repository structure and files as shown above
  2. Create a new Heroku app: heroku create fastapi-todo-demo
  3. Initialize Git and commit your files:
    bash
    git init
    git add .
    git commit -m "Initial commit of Todo API"
  4. Add Heroku as a remote and deploy:
    bash
    heroku git:remote -a fastapi-todo-demo
    git push heroku main
  5. Open your application: heroku open

Summary

In this tutorial, you've learned how to:

  1. Prepare your FastAPI application for Heroku deployment
  2. Create the necessary configuration files (Procfile, runtime.txt)
  3. Deploy your application to Heroku using Git
  4. Test your deployed API endpoints
  5. Configure environment variables
  6. Scale and monitor your application
  7. Implement continuous deployment through GitHub
  8. Build a real-world Todo API example

Deploying your FastAPI application on Heroku provides a reliable and scalable way to make your API accessible to users around the world. By following the steps in this tutorial, you should be able to deploy your own FastAPI applications with confidence.

Additional Resources

Practice Exercises

  1. Modify the Todo API to use a PostgreSQL database with SQLAlchemy instead of the in-memory database
  2. Add user authentication to the Todo API using OAuth2 with JWT tokens
  3. Create a frontend application using React or Vue.js that consumes your deployed FastAPI Todo API
  4. Implement rate limiting on your API to prevent abuse
  5. Add a custom domain name to your Heroku application


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)