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:
- A working FastAPI application
- Git installed on your machine
- A Heroku account (free to create)
- 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:
Procfile
- Tells Heroku how to run your applicationruntime.txt
- Specifies the Python versionrequirements.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:
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:
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:
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:
git init
git add .
git commit -m "Initial commit for Heroku deployment"
Add Heroku Remote
Add the Heroku remote to your Git repository:
heroku git:remote -a your-app-name
Push to Heroku
Deploy your application by pushing to the Heroku remote:
git push heroku main
If you're using the master
branch instead of main
, use:
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:
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:
curl -X 'GET' \
'https://your-app-name.herokuapp.com/items/' \
-H 'accept: application/json'
Expected output:
[
{"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:
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:
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:
heroku ps
Scaling Up or Down
You can scale your application by adding more dynos (note that this may incur charges):
heroku ps:scale web=2
To scale back down:
heroku ps:scale web=1
Step 8: Monitoring Your Application
Viewing Logs
To view the logs from your application:
heroku logs --tail
Checking Application Metrics
You can monitor your application's performance in the Heroku Dashboard:
- Go to https://dashboard.heroku.com/apps
- Select your application
- Navigate to the "Metrics" tab
Step 9: Common Issues and Troubleshooting
Application Crashes
If your application crashes after deployment, check the logs:
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:
- Go to your app in the Heroku Dashboard
- Go to the "Deploy" tab
- Select "GitHub" as the deployment method
- Connect to your GitHub repository
- 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
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
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
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
- Create the repository structure and files as shown above
- Create a new Heroku app:
heroku create fastapi-todo-demo
- Initialize Git and commit your files:
bash
git init
git add .
git commit -m "Initial commit of Todo API" - Add Heroku as a remote and deploy:
bash
heroku git:remote -a fastapi-todo-demo
git push heroku main - Open your application:
heroku open
Summary
In this tutorial, you've learned how to:
- Prepare your FastAPI application for Heroku deployment
- Create the necessary configuration files (Procfile, runtime.txt)
- Deploy your application to Heroku using Git
- Test your deployed API endpoints
- Configure environment variables
- Scale and monitor your application
- Implement continuous deployment through GitHub
- 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
- Official FastAPI Documentation
- Heroku Python Support Documentation
- FastAPI GitHub Repository
- Uvicorn Documentation
Practice Exercises
- Modify the Todo API to use a PostgreSQL database with SQLAlchemy instead of the in-memory database
- Add user authentication to the Todo API using OAuth2 with JWT tokens
- Create a frontend application using React or Vue.js that consumes your deployed FastAPI Todo API
- Implement rate limiting on your API to prevent abuse
- 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! :)