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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)