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
mkdir my-fastapi-appcd my-fastapi-apppython3 -m venv venvsource venv/bin/activate # On Windows: venv\Scripts\activateStep 2: Install FastAPI and Dependencies
pip install fastapi uvicorn[standard] sqlalchemy psycopg2-binary python-decouple pydanticKey packages:
fastapi: The web frameworkuvicorn[standard]: ASGI server with HTTP/WebSocket supportsqlalchemy: SQL toolkit and ORMpsycopg2-binary: PostgreSQL database adapterpython-decouple: Environment variable managementpydantic: Data validation using Python type hints
Step 3: Create Your FastAPI Application
Create main.py:
from fastapi import FastAPI, HTTPException, Depends, Queryfrom fastapi.responses import JSONResponsefrom fastapi.middleware.cors import CORSMiddlewarefrom sqlalchemy import create_engine, Column, Integer, String, DateTimefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmaker, Sessionfrom pydantic import BaseModel, Fieldfrom decouple import configfrom datetime import datetimeimport os
# ConfigurationDATABASE_URL = config('DATABASE_URL', default='sqlite:///./test.db')ALLOWED_ORIGINS = config('ALLOWED_ORIGINS', default='http://localhost:3000,http://localhost:8000').split(',')
# Database setupengine = 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()
# Modelsclass 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 schemasclass 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 appapp = FastAPI( title="FastAPI on Klutch.sh", description="Production-ready FastAPI application", version="1.0.0")
# CORS configurationapp.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)
# Dependencydef 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
pip freeze > requirements.txtYour requirements.txt should contain:
fastapi==0.104.1uvicorn[standard]==0.24.0sqlalchemy==2.0.23psycopg2-binary==2.9.9python-decouple==3.8pydantic==2.5.0pydantic-settings==2.1.0Step 5: Test Locally
Create a .env file for local development:
DATABASE_URL=sqlite:///./test.dbALLOWED_ORIGINS=http://localhost:8000,http://localhost:3000Run the application:
uvicorn main:app --reload --host 0.0.0.0 --port 8000Access the interactive documentation at http://localhost:8000/docs (Swagger UI) or http://localhost:8000/redoc (ReDoc).
Test the health endpoint:
curl http://localhost:8000/healthDeploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your FastAPI application from your source code.
Prepare Your Repository
- Initialize a Git repository and commit your code:
git initgit add .git commit -m "Initial FastAPI app commit"- Create a
.gitignorefile:
venv/__pycache__/*.pyc*.pyo*.egg-info/.env.DS_Store*.db*.sqlite3- Push to GitHub:
git remote add origin https://github.com/YOUR_USERNAME/my-fastapi-app.gitgit branch -M maingit push -u origin mainDeploy to Klutch.sh
-
Log in to Klutch.sh dashboard.
-
Click “Create a new project” and provide a project name.
-
Inside your project, click “Create a new app”.
-
Repository Configuration:
- Select your GitHub repository containing the FastAPI app
- Select the branch to deploy (typically
main)
-
Traffic Settings:
- Select “HTTP” as the traffic type
-
Port Configuration:
- Set the internal port to 8000 (the port the FastAPI app listens on)
-
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 to1to ensure Python output is logged immediately
-
Build and Start Commands: If you need to customize the build or start command, set these environment variables:
BUILD_COMMAND: Default runspip install -r requirements.txtSTART_COMMAND: Default isuvicorn 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 -
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)
-
Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.
-
Once deployment completes, your app will be accessible at
example-app.klutch.sh.
Verifying the Deployment
Test your deployed app:
curl https://example-app.klutch.sh/healthYou 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 stageFROM python:3.11-slim as builder
WORKDIR /app
# Install system dependenciesRUN 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 dependenciesCOPY requirements.txt .RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stageFROM python:3.11-slim
WORKDIR /app
# Install runtime dependenciesRUN apt-get update && apt-get install -y --no-install-recommends \ libpq5 \ curl \ && rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builderCOPY --from=builder /root/.local /root/.local
# Set PATH to use pip from builderENV PATH=/root/.local/bin:$PATHENV PYTHONUNBUFFERED=1
# Copy application codeCOPY . .
# Create non-root user for securityRUN useradd -m -u 1000 fastapi_user && \ chown -R fastapi_user:fastapi_user /app
USER fastapi_user
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1
# Expose portEXPOSE 8000
# Start the applicationCMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]Deploy the Dockerfile Version
- Push your code with the Dockerfile to GitHub:
git add Dockerfilegit commit -m "Add Dockerfile for custom build"git push-
Log in to Klutch.sh dashboard.
-
- 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”
-
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:
- Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
- Get the connection details from the PostgreSQL dashboard
- 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 configfrom 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, WebSocketDisconnectfrom 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 BackgroundTasksimport 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_itemAuthentication and Security
OAuth2 with JWT
Add JWT authentication:
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestFormfrom jose import JWTError, jwtfrom passlib.context import CryptContextfrom datetime import datetime, timedeltafrom typing import Optional
# Security configurationSECRET_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:
| Variable | Description | Example |
|---|---|---|
DATABASE_URL | PostgreSQL connection string | postgresql://user:pass@host:5432/db |
ALLOWED_ORIGINS | CORS allowed origins | https://example-app.klutch.sh |
SECRET_KEY | JWT secret key | Auto-generate with openssl rand -hex 32 |
PYTHONUNBUFFERED | Enable unbuffered logging | 1 |
LOG_LEVEL | Application logging level | INFO |
Customization Environment Variables (Nixpacks)
For Nixpacks deployments:
| Variable | Purpose | Example |
|---|---|---|
BUILD_COMMAND | Build command | pip install -r requirements.txt |
START_COMMAND | Start command | uvicorn main:app --host 0.0.0.0 --port $PORT --workers 4 |
Persistent Storage for Files and Logs
Adding Persistent Volume
- In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
- Click “Add Volume”
- Set the mount path:
/app/logs(for application logs) or/app/data(for uploaded files) - Set the size based on your needs (e.g., 10 GB for logs, 50 GB for user uploads)
- Save and redeploy
Logging to Persistent Storage
Update your FastAPI app to log to a file:
import loggingimport 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:
- In the Klutch.sh app dashboard, navigate to “Custom Domains”
- Click “Add Custom Domain”
- Enter your domain (e.g.,
api.example.com) - Follow the DNS configuration instructions provided
- Update
ALLOWED_ORIGINSto include your custom domain
Example DNS configuration:
api.example.com CNAME example-app.klutch.shUpdate environment variable:
ALLOWED_ORIGINS=https://api.example.com,https://example-app.klutch.shMonitoring 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 responsePerformance Monitoring
- Access metrics from Klutch.sh dashboard
- Monitor response times and error rates
- 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
- Never commit secrets: Use environment variables for sensitive data
- HTTPS only: Always use HTTPS in production
- CORS configuration: Restrict origins to trusted domains
- Input validation: Use Pydantic models for automatic validation
- SQL injection prevention: Always use parameterized queries (SQLAlchemy ORM)
- Rate limiting: Implement rate limiting for API endpoints
- Authentication: Use strong authentication methods (OAuth2, JWT)
- HTTPS redirects: Enable HTTPS enforcement
- Security headers: Set proper security headers
- Dependency updates: Keep dependencies updated for security patches
Example security configuration:
from fastapi.middleware.trustedhost import TrustedHostMiddlewarefrom 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
--workersparameter - 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
-
Use Multiple Workers: Scale with multiple Uvicorn workers
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 -
Implement Request Validation: Use Pydantic models extensively
class Item(BaseModel):name: str = Field(..., min_length=1)price: int = Field(..., gt=0) -
Enable CORS Selectively: Only allow trusted origins
allow_origins=[config('ALLOWED_ORIGINS').split(',')] -
Use Database Connection Pooling: Configure SQLAlchemy pooling
engine = create_engine(DATABASE_URL,pool_size=20,max_overflow=0) -
Implement Structured Logging: Log important events
logger.info(f"Processing request: {request.id}") -
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) -
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] -
Version Your API: Plan for API evolution
@app.get("/api/v1/items")async def get_items_v1(): pass -
Monitor Dependencies: Keep packages updated
Terminal window pip list --outdated -
Test Thoroughly: Write comprehensive tests
Terminal window pytest tests/
Resources
- FastAPI Official Documentation
- FastAPI Tutorial
- SQLAlchemy Documentation
- Uvicorn ASGI Server
- Pydantic Documentation
- PostgreSQL Documentation
- FastAPI Deployment Guide
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.