Skip to content

Deploying a FastAPI App

What is FastAPI?

FastAPI is a modern, high-performance Python web framework for building APIs with automatic documentation and data validation. Built on standard Python type hints and asynchronous capabilities, FastAPI makes it easy to create production-ready REST APIs and microservices with minimal boilerplate code.

Key features include:

  • Automatic OpenAPI/Swagger documentation generation
  • Built-in request validation using Pydantic models
  • Asynchronous (async/await) request handling for high performance
  • Type hints for automatic documentation and IDE support
  • WebSocket support for real-time communication
  • Dependency injection system for code organization
  • CORS, HTTPS redirects, and other security features
  • Background task support with BackgroundTasks
  • Support for multiple authentication methods (OAuth2, JWT)
  • Database ORM integration (SQLAlchemy, Tortoise ORM, etc.)
  • Testing utilities for automated testing
  • JSON serialization with custom encoders
  • Response models for output validation
  • Cookie and header management
  • Form data and file upload handling
  • Streaming responses
  • Middleware support for request/response processing

FastAPI is ideal for building RESTful APIs, GraphQL servers, microservices, real-time applications with WebSockets, and data-driven web applications requiring high performance and automatic documentation.

Prerequisites

Before deploying a FastAPI application to Klutch.sh, ensure you have:

  • Python 3.9+ installed on your local machine
  • pip or conda for dependency management
  • Git and a GitHub account
  • A Klutch.sh account with dashboard access
  • Basic understanding of async Python programming
  • Optional: PostgreSQL or other database server for data persistence

Getting Started with FastAPI

Step 1: Create Your Project Directory and Virtual Environment

Terminal window
mkdir my-fastapi-app
cd my-fastapi-app
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate

Step 2: Install FastAPI and Dependencies

Terminal window
pip install fastapi uvicorn[standard] sqlalchemy psycopg2-binary python-decouple pydantic

Key packages:

  • fastapi: The web framework
  • uvicorn[standard]: ASGI server with HTTP/WebSocket support
  • sqlalchemy: SQL toolkit and ORM
  • psycopg2-binary: PostgreSQL database adapter
  • python-decouple: Environment variable management
  • pydantic: Data validation using Python type hints

Step 3: Create Your FastAPI Application

Create main.py:

from fastapi import FastAPI, HTTPException, Depends, Query
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from pydantic import BaseModel, Field
from decouple import config
from datetime import datetime
import os
# Configuration
DATABASE_URL = config('DATABASE_URL', default='sqlite:///./test.db')
ALLOWED_ORIGINS = config('ALLOWED_ORIGINS', default='http://localhost:3000,http://localhost:8000').split(',')
# Database setup
engine = create_engine(DATABASE_URL, connect_args={'check_same_thread': False} if 'sqlite' in DATABASE_URL else {})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Models
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), index=True)
description = Column(String(500))
price = Column(Integer)
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(bind=engine)
# Pydantic schemas
class ItemBase(BaseModel):
name: str = Field(..., min_length=1, max_length=100, description="Item name")
description: str = Field(..., max_length=500, description="Item description")
price: int = Field(..., gt=0, description="Item price in cents")
class ItemCreate(ItemBase):
pass
class ItemUpdate(BaseModel):
name: str | None = None
description: str | None = None
price: int | None = None
class ItemResponse(ItemBase):
id: int
created_at: datetime
class Config:
from_attributes = True
# FastAPI app
app = FastAPI(
title="FastAPI on Klutch.sh",
description="Production-ready FastAPI application",
version="1.0.0"
)
# CORS configuration
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Routes
@app.get("/health", tags=["Health"])
async def health_check():
"""Health check endpoint for monitoring."""
return {
"status": "healthy",
"service": "fastapi-app"
}
@app.get("/items", response_model=list[ItemResponse], tags=["Items"])
async def list_items(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100),
db: Session = Depends(get_db)
):
"""Get list of items with pagination."""
items = db.query(Item).offset(skip).limit(limit).all()
return items
@app.get("/items/{item_id}", response_model=ItemResponse, tags=["Items"])
async def get_item(item_id: int, db: Session = Depends(get_db)):
"""Get a single item by ID."""
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return item
@app.post("/items", response_model=ItemResponse, status_code=201, tags=["Items"])
async def create_item(item: ItemCreate, db: Session = Depends(get_db)):
"""Create a new item."""
db_item = Item(**item.dict())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
@app.put("/items/{item_id}", response_model=ItemResponse, tags=["Items"])
async def update_item(item_id: int, item: ItemUpdate, db: Session = Depends(get_db)):
"""Update an existing item."""
db_item = db.query(Item).filter(Item.id == item_id).first()
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
update_data = item.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(db_item, field, value)
db.commit()
db.refresh(db_item)
return db_item
@app.delete("/items/{item_id}", status_code=204, tags=["Items"])
async def delete_item(item_id: int, db: Session = Depends(get_db)):
"""Delete an item."""
db_item = db.query(Item).filter(Item.id == item_id).first()
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(db_item)
db.commit()
return None
@app.get("/stats", tags=["Analytics"])
async def get_stats(db: Session = Depends(get_db)):
"""Get application statistics."""
total_items = db.query(Item).count()
total_value = sum(item.price for item in db.query(Item).all()) or 0
return {
"total_items": total_items,
"total_value": total_value / 100, # Convert cents to dollars
"average_price": (total_value / total_items / 100) if total_items > 0 else 0
}
if __name__ == "__main__":
import uvicorn
port = int(os.getenv('PORT', 8000))
uvicorn.run(app, host="0.0.0.0", port=port)

Step 4: Create a Requirements File

Terminal window
pip freeze > requirements.txt

Your requirements.txt should contain:

fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-decouple==3.8
pydantic==2.5.0
pydantic-settings==2.1.0

Step 5: Test Locally

Create a .env file for local development:

DATABASE_URL=sqlite:///./test.db
ALLOWED_ORIGINS=http://localhost:8000,http://localhost:3000

Run the application:

Terminal window
uvicorn main:app --reload --host 0.0.0.0 --port 8000

Access the interactive documentation at http://localhost:8000/docs (Swagger UI) or http://localhost:8000/redoc (ReDoc).

Test the health endpoint:

Terminal window
curl http://localhost:8000/health

Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your FastAPI application from your source code.

Prepare Your Repository

  1. Initialize a Git repository and commit your code:
Terminal window
git init
git add .
git commit -m "Initial FastAPI app commit"
  1. Create a .gitignore file:
venv/
__pycache__/
*.pyc
*.pyo
*.egg-info/
.env
.DS_Store
*.db
*.sqlite3
  1. Push to GitHub:
Terminal window
git remote add origin https://github.com/YOUR_USERNAME/my-fastapi-app.git
git branch -M main
git push -u origin main

Deploy to Klutch.sh

  1. Log in to Klutch.sh dashboard.

  2. Click “Create a new project” and provide a project name.

  3. Inside your project, click “Create a new app”.

  4. Repository Configuration:

    • Select your GitHub repository containing the FastAPI app
    • Select the branch to deploy (typically main)
  5. Traffic Settings:

    • Select “HTTP” as the traffic type
  6. Port Configuration:

    • Set the internal port to 8000 (the port the FastAPI app listens on)
  7. Environment Variables: Set the following environment variables in the Klutch.sh dashboard:

    • DATABASE_URL: Your PostgreSQL connection string (e.g., postgresql://user:password@postgres-host:5432/fastapi_db)
    • ALLOWED_ORIGINS: CORS allowed origins (e.g., https://example-app.klutch.sh,https://myapp.example.com)
    • PYTHONUNBUFFERED: Set to 1 to ensure Python output is logged immediately
  8. Build and Start Commands: If you need to customize the build or start command, set these environment variables:

    • BUILD_COMMAND: Default runs pip install -r requirements.txt
    • START_COMMAND: Default is uvicorn main:app --host 0.0.0.0 --port $PORT --workers 4

    For example, to run database migrations before starting:

    START_COMMAND=uvicorn main:app --host 0.0.0.0 --port $PORT --workers 4
  9. Region, Compute, and Instances:

    • Choose your desired region for optimal latency
    • Select compute resources (Starter for prototypes, Pro/Premium for production)
    • Set the number of instances (start with 1-2 for testing, scale as needed)
  10. Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.

  11. Once deployment completes, your app will be accessible at example-app.klutch.sh.

Verifying the Deployment

Test your deployed app:

Terminal window
curl https://example-app.klutch.sh/health

You should receive:

{
"status": "healthy",
"service": "fastapi-app"
}

Access the API documentation at: https://example-app.klutch.sh/docs


Deploying With a Dockerfile

If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.

Create a Multi-Stage Dockerfile

Create a Dockerfile in your project root:

# Build stage
FROM python:3.11-slim as builder
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.11-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local
# Set PATH to use pip from builder
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
# Copy application code
COPY . .
# Create non-root user for security
RUN useradd -m -u 1000 fastapi_user && \
chown -R fastapi_user:fastapi_user /app
USER fastapi_user
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Expose port
EXPOSE 8000
# Start the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

Deploy the Dockerfile Version

  1. Push your code with the Dockerfile to GitHub:
Terminal window
git add Dockerfile
git commit -m "Add Dockerfile for custom build"
git push
  1. Log in to Klutch.sh dashboard.

  2. Create a new app:

    • Select your GitHub repository and branch
    • Set traffic type to “HTTP”
    • Set the internal port to 8000
    • Add environment variables (same as Nixpacks deployment)
    • Click “Create”
  3. Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.


Database Configuration

PostgreSQL Setup

PostgreSQL is the recommended database for FastAPI applications. To use a PostgreSQL instance with Klutch.sh:

  1. Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
  2. Get the connection details from the PostgreSQL dashboard
  3. Set the DATABASE_URL environment variable:
    postgresql://user:password@postgres-host:5432/fastapi_db

Update your main.py to use the DATABASE_URL:

from decouple import config
from sqlalchemy import create_engine
DATABASE_URL = config('DATABASE_URL', default='sqlite:///./test.db')
engine = create_engine(DATABASE_URL)

SQLite for Development

For local development and testing:

DATABASE_URL = 'sqlite:///./test.db'

SQLite is not recommended for production use on Klutch.sh due to lack of concurrent write support.


WebSockets and Real-Time Communication

FastAPI has built-in WebSocket support. Add WebSocket routes to main.py:

from fastapi import WebSocket, WebSocketDisconnect
from typing import List
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.broadcast(f"Message: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)

Background Tasks

FastAPI supports background tasks for non-blocking operations:

from fastapi import BackgroundTasks
import logging
logger = logging.getLogger(__name__)
def write_notification(item_id: int, message: str = ""):
logger.info(f"Writing notification for item {item_id}: {message}")
@app.post("/items/", response_model=ItemResponse)
async def create_item_with_notification(
item: ItemCreate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db)
):
"""Create item and send notification in background."""
db_item = Item(**item.dict())
db.add(db_item)
db.commit()
db.refresh(db_item)
background_tasks.add_task(write_notification, db_item.id, message="Item created")
return db_item

Authentication and Security

OAuth2 with JWT

Add JWT authentication:

from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional
# Security configuration
SECRET_KEY = config('SECRET_KEY', default='dev-secret-key-change-in-production')
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise HTTPException(status_code=401, detail="Invalid token")
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
return username
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
"""User login endpoint."""
# In production, verify credentials against database
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": form_data.username},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def get_user_profile(current_user: str = Depends(get_current_user)):
"""Get current user profile (protected endpoint)."""
return {"username": current_user}

Environment Variables and Configuration

Essential Environment Variables

Configure these variables in the Klutch.sh dashboard:

VariableDescriptionExample
DATABASE_URLPostgreSQL connection stringpostgresql://user:pass@host:5432/db
ALLOWED_ORIGINSCORS allowed originshttps://example-app.klutch.sh
SECRET_KEYJWT secret keyAuto-generate with openssl rand -hex 32
PYTHONUNBUFFEREDEnable unbuffered logging1
LOG_LEVELApplication logging levelINFO

Customization Environment Variables (Nixpacks)

For Nixpacks deployments:

VariablePurposeExample
BUILD_COMMANDBuild commandpip install -r requirements.txt
START_COMMANDStart commanduvicorn main:app --host 0.0.0.0 --port $PORT --workers 4

Persistent Storage for Files and Logs

Adding Persistent Volume

  1. In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
  2. Click “Add Volume”
  3. Set the mount path: /app/logs (for application logs) or /app/data (for uploaded files)
  4. Set the size based on your needs (e.g., 10 GB for logs, 50 GB for user uploads)
  5. Save and redeploy

Logging to Persistent Storage

Update your FastAPI app to log to a file:

import logging
import os
log_dir = os.getenv('LOG_DIR', '/app/logs')
os.makedirs(log_dir, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(log_dir, 'fastapi.log')),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)

Custom Domains

To serve your FastAPI application from a custom domain:

  1. In the Klutch.sh app dashboard, navigate to “Custom Domains”
  2. Click “Add Custom Domain”
  3. Enter your domain (e.g., api.example.com)
  4. Follow the DNS configuration instructions provided
  5. Update ALLOWED_ORIGINS to include your custom domain

Example DNS configuration:

api.example.com CNAME example-app.klutch.sh

Update environment variable:

ALLOWED_ORIGINS=https://api.example.com,https://example-app.klutch.sh

Monitoring and Logging

Application Logging

Configure logging in your FastAPI app:

import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
@app.middleware("http")
async def log_requests(request, call_next):
logger.info(f"{request.method} {request.url}")
response = await call_next(request)
logger.info(f"Response status: {response.status_code}")
return response

Performance Monitoring

  1. Access metrics from Klutch.sh dashboard
  2. Monitor response times and error rates
  3. Check memory and CPU usage

Health Checks

The included health check endpoint is already configured:

@app.get("/health")
async def health_check():
return {
"status": "healthy",
"service": "fastapi-app"
}

Security Best Practices

  1. Never commit secrets: Use environment variables for sensitive data
  2. HTTPS only: Always use HTTPS in production
  3. CORS configuration: Restrict origins to trusted domains
  4. Input validation: Use Pydantic models for automatic validation
  5. SQL injection prevention: Always use parameterized queries (SQLAlchemy ORM)
  6. Rate limiting: Implement rate limiting for API endpoints
  7. Authentication: Use strong authentication methods (OAuth2, JWT)
  8. HTTPS redirects: Enable HTTPS enforcement
  9. Security headers: Set proper security headers
  10. Dependency updates: Keep dependencies updated for security patches

Example security configuration:

from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app.add_middleware(HTTPSRedirectMiddleware)
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')
)

Troubleshooting

Issue 1: Database Connection Errors

Problem: “Connection refused” or “database not found” errors.

Solution:

  • Verify DATABASE_URL is correctly configured
  • Ensure PostgreSQL instance is running and accessible
  • Check database user has proper permissions
  • Test connection: psql $DATABASE_URL

Issue 2: CORS Errors in Frontend

Problem: Frontend requests receive CORS errors.

Solution:

  • Add frontend origin to ALLOWED_ORIGINS environment variable
  • Verify CORS middleware is properly configured
  • Check request includes correct Origin header
  • Ensure Access-Control-Allow-Credentials is set if needed

Issue 3: WebSocket Connection Issues

Problem: WebSocket connections fail or disconnect unexpectedly.

Solution:

  • Verify WebSocket endpoint is properly defined
  • Check firewall allows WebSocket connections
  • Enable connection pooling for database queries
  • Implement connection heartbeat/ping-pong

Issue 4: Slow API Response Times

Problem: API endpoints respond slowly.

Solution:

  • Add database query optimization (indexing, pagination)
  • Implement caching with Redis
  • Use background tasks for long operations
  • Monitor and profile slow endpoints
  • Scale to multiple workers/instances

Issue 5: Out of Memory Errors

Problem: Application crashes with out of memory errors.

Solution:

  • Reduce worker count: reduce --workers parameter
  • Limit request size in middleware
  • Implement streaming for large responses
  • Add memory limits in Dockerfile
  • Scale instances if consistently memory-constrained

Best Practices for Production Deployment

  1. Use Multiple Workers: Scale with multiple Uvicorn workers

    uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
  2. Implement Request Validation: Use Pydantic models extensively

    class Item(BaseModel):
    name: str = Field(..., min_length=1)
    price: int = Field(..., gt=0)
  3. Enable CORS Selectively: Only allow trusted origins

    allow_origins=[config('ALLOWED_ORIGINS').split(',')]
  4. Use Database Connection Pooling: Configure SQLAlchemy pooling

    engine = create_engine(
    DATABASE_URL,
    pool_size=20,
    max_overflow=0
    )
  5. Implement Structured Logging: Log important events

    logger.info(f"Processing request: {request.id}")
  6. Add Request ID Tracking: Track requests through logs

    @app.middleware("http")
    async def add_request_id(request, call_next):
    request.state.id = str(uuid.uuid4())
    return await call_next(request)
  7. Use Type Hints: Leverage FastAPI’s type hint benefits

    async def get_items(skip: int = 0, limit: int = 10) -> List[Item]:
    return items[skip:skip + limit]
  8. Version Your API: Plan for API evolution

    @app.get("/api/v1/items")
    async def get_items_v1(): pass
  9. Monitor Dependencies: Keep packages updated

    Terminal window
    pip list --outdated
  10. Test Thoroughly: Write comprehensive tests

    Terminal window
    pytest tests/

Resources


Conclusion

Deploying FastAPI applications to Klutch.sh provides a high-performance platform for building and running modern Python APIs. FastAPI’s async capabilities, automatic documentation, and built-in validation make it an excellent choice for production applications.

Key takeaways:

  • Use Nixpacks for quick deployments with automatic Python detection
  • Use Docker for complete control over the build and runtime environment
  • Leverage FastAPI’s type hints and Pydantic models for automatic validation
  • Configure database connections through environment variables
  • Implement proper authentication and CORS for security
  • Use persistent storage for logs and file uploads
  • Monitor application health and performance
  • Scale workers and instances based on traffic patterns
  • Keep dependencies updated for security and performance

For additional help, refer to the FastAPI documentation or Klutch.sh support resources.