FastAPI Production Deployment: Docker and Cloud Guide
FastAPI has rapidly become one of the most popular Python web frameworks, offering automatic OpenAPI documentation, type-safe request validation, and excellent async performance. Deploying a FastAPI application to production correctly requires containerization with Docker, a production ASGI server, and proper environment configuration.
This guide covers everything you need to get a FastAPI app running reliably in production on [PandaStack](https://pandastack.io).
Why FastAPI Needs Uvicorn in Production
FastAPI is an ASGI framework — it needs an ASGI server to handle HTTP requests. In development, FastAPI's built-in uvicorn runner works fine. In production, you should run Uvicorn under Gunicorn with the Uvicorn worker class, which adds process management and multi-worker support:
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000This gives you Gunicorn's robust process management with Uvicorn's async request handling.
Project Structure
myapi/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── routers/
│ │ └── users.py
│ ├── models.py
│ └── database.py
├── requirements.txt
├── Dockerfile
└── pandastack.jsonApplication Entry Point
# app/main.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
from .database import database
from .routers import users
@asynccontextmanager
async def lifespan(app: FastAPI):
await database.connect()
yield
await database.disconnect()
app = FastAPI(
title="My API",
lifespan=lifespan,
docs_url="/docs" if not os.getenv("PRODUCTION") else None,
)
app.include_router(users.router, prefix="/api/v1")
@app.get("/health")
async def health():
return {"status": "ok"}The lifespan context manager connects your database pool on startup and disconnects cleanly on shutdown — the correct pattern for async FastAPI apps.
Production Dockerfile
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]Requirements
fastapi>=0.110.0
uvicorn[standard]>=0.29.0
gunicorn>=21.2.0
asyncpg>=0.29.0
databases[asyncpg]>=0.9.0
pydantic-settings>=2.0.0Async Database with asyncpg
FastAPI's async performance is only realized if your database calls are also async. Use databases with asyncpg for PostgreSQL:
# app/database.py
import os
import databases
DATABASE_URL = os.environ["DATABASE_URL"]
database = databases.Database(DATABASE_URL)Provision a managed PostgreSQL instance from the Databases section of [dashboard.pandastack.io](https://dashboard.pandastack.io) and copy the connection string.
Configuring pandastack.json
{
"type": "container",
"healthCheckPath": "/health"
}PandaStack polls the healthCheckPath after each deployment. Only after your container returns HTTP 200 from this endpoint does PandaStack route live traffic to it. This prevents users from hitting your app before the database connection pool is ready.
Environment Variables
Set all secrets and configuration from [dashboard.pandastack.io](https://dashboard.pandastack.io):
DATABASE_URL=postgresql+asyncpg://user:pass@host:5432/mydb
SECRET_KEY=your-jwt-secret-key
PRODUCTION=true
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.comRead these in your FastAPI app using Pydantic Settings:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
production: bool = False
allowed_origins: list[str] = []
class Config:
env_file = ".env"
settings = Settings()CORS Configuration
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)Deploying with GitHub Integration
Connect your GitHub repository from the PandaStack dashboard. Every push to your main branch triggers an automatic build and deploy. For CLI-based deployments:
npm install -g @pandastack/cli
panda deployDisabling Interactive Docs in Production
FastAPI's /docs and /redoc endpoints are useful in development but should be disabled or protected in production:
app = FastAPI(
docs_url=None if settings.production else "/docs",
redoc_url=None if settings.production else "/redoc",
)Production Checklist
- Gunicorn + Uvicorn worker class handles concurrent requests
- Database uses async connection pool via
lifespan - All secrets stored as environment variables
/healthendpoint returns HTTP 200- Interactive API docs disabled in production
- Container runs as non-root user
pandastack.jsonspecifieshealthCheckPath
Background Tasks with FastAPI
FastAPI doesn't include a built-in task queue, but integrates well with Celery or ARQ for background processing. For simpler use cases, FastAPI's BackgroundTasks runs tasks after sending a response:
from fastapi import BackgroundTasks
def send_welcome_email(email: str):
# blocking email call runs after response is sent
mailer.send(to=email, subject="Welcome!")
@app.post("/users")
async def create_user(user: UserCreate, background_tasks: BackgroundTasks):
db_user = await create_user_in_db(user)
background_tasks.add_task(send_welcome_email, user.email)
return db_userFor heavier workloads, deploy a separate Celery worker container on PandaStack using the same Docker image with a different command, backed by a Redis instance from the PandaStack Databases section.
For full documentation, visit [docs.pandastack.io](https://docs.pandastack.io).