Deploy FastAPI with AZIN
FastAPI production deployment means configuring an ASGI server, managing async database connections, handling environment variables, and keeping workers alive under load. AZIN handles all of it: Railpack auto-detects FastAPI, configures Uvicorn, provisions PostgreSQL and Redis in your GCP account, and deploys on every git push. No Nginx. No Dockerfile. Push code, it's live.
#How AZIN detects FastAPI
AZIN uses Railpack to auto-detect your project type. When it finds a Python dependency file with fastapi in your dependencies, it identifies a FastAPI application and configures an ASGI-appropriate build.
Detection files Railpack looks for:
requirements.txtpyproject.tomlPipfilesetup.py
Once detected, Railpack resolves your Python version from .python-version first, then runtime.txt, and defaults to Python 3.13 if neither is present. Package manager detection is automatic — pip, Poetry, and Pipenv are all supported.
What Railpack configures automatically for FastAPI:
- Python version from
.python-versionorruntime.txt(default: 3.13) - Dependencies installed via pip, Poetry, or Pipenv based on your lockfile
- Uvicorn as the ASGI server — not Gunicorn, because FastAPI is an ASGI framework
- Start command set to
uvicorn main:app --host 0.0.0.0 --port $PORT
No Procfile, no Dockerfile, no manual server configuration. FastAPI is ASGI-native, so Railpack uses Uvicorn directly rather than wrapping it behind a WSGI server. If you already have a Dockerfile, AZIN supports that too — but for most FastAPI apps, Railpack handles everything.
#Deployment config
Connect your GitHub repository to AZIN and it deploys on every push. For a FastAPI app with PostgreSQL and Redis:
name: my-fastapi-app
cloud: gcp
region: us-central1
services:
api:
build:
type: railpack
env:
ENVIRONMENT: production
PORT: "8000"
scaling:
min: 1
max: 10
target_cpu: 70
db:
type: postgres
plan: production
cache:
type: redisAZIN injects DATABASE_URL and REDIS_URL into your API service automatically. The db service provisions Cloud SQL in your GCP account. The cache service provisions Memorystore. Both live in your cloud, not on shared infrastructure.
API with background worker
For an API with a background worker processing tasks from a Redis queue:
name: my-fastapi-app
cloud: gcp
region: us-central1
services:
api:
build:
type: railpack
env:
ENVIRONMENT: production
PORT: "8000"
scaling:
min: 1
max: 10
target_cpu: 70
worker:
build:
type: railpack
start: arq app.worker.WorkerSettings
env:
ENVIRONMENT: production
db:
type: postgres
plan: production
cache:
type: redisBoth services share the same codebase, the same DATABASE_URL, and the same REDIS_URL. The worker service overrides the start command to run your task processor instead of Uvicorn.
#FastAPI app configuration for production
FastAPI uses Pydantic for settings management. Define a Settings class that reads from environment variables — this keeps secrets out of your codebase and lets AZIN inject values at runtime:
# app/config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
environment: str = "development"
database_url: str
redis_url: str = ""
secret_key: str
allowed_origins: list[str] = ["https://yourdomain.com"]
model_config = {"env_file": ".env"}
settings = Settings()Your main application file wires up the settings and configures FastAPI for production:
# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from app.config import settings
from app.database import engine
@asynccontextmanager
async def lifespan(app: FastAPI):
async with engine.begin() as conn:
await conn.execute(text("SELECT 1"))
yield
await engine.dispose()
app = FastAPI(
title="My API",
docs_url="/docs" if settings.environment != "production" else None,
redoc_url=None,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_methods=["*"],
allow_headers=["*"],
)Key points:
pydantic-settingsreadsDATABASE_URLandREDIS_URLfrom the environment — both injected by AZIN automatically- The
lifespancontext manager replaces the deprecatedon_eventdecorators and handles startup/shutdown cleanly - Disable
/docsin production to avoid exposing your API schema publicly - CORS origins should be restricted to your actual frontend domains
Your requirements.txt for a FastAPI app with PostgreSQL and Redis:
fastapi>=0.115
uvicorn[standard]>=0.32
pydantic-settings>=2.6
sqlalchemy[asyncio]>=2.0
asyncpg>=0.30
redis>=5.0
alembic>=1.14
uvicorn[standard] includes uvloop and httptools — both provide meaningful performance improvements over the pure-Python defaults.
Info
uvicorn main:app --reload in production. The --reload flag watches for file changes and restarts the server — useful for development, wasteful and unstable in production. Railpack configures Uvicorn without --reload automatically.#Async database access
FastAPI is async-native, so your database layer should be too. SQLAlchemy 2.0+ with asyncpg gives you a fully async PostgreSQL connection pool:
# app/database.py
from sqlalchemy.ext.asyncio import (
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from app.config import settings
# Convert postgres:// to postgresql+asyncpg://
database_url = settings.database_url.replace(
"postgres://", "postgresql+asyncpg://"
)
engine = create_async_engine(
database_url,
pool_size=20,
max_overflow=10,
pool_pre_ping=True,
pool_recycle=300,
)
async_session = async_sessionmaker(engine, class_=AsyncSession)
async def get_db():
async with async_session() as session:
yield sessionUse the session in your route handlers via FastAPI's dependency injection:
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.models import User
router = APIRouter()
@router.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404)
return userpool_pre_ping prevents stale connection errors after Cloud SQL restarts. pool_recycle=300 closes connections older than 5 minutes — Cloud SQL's proxy idle timeout is 10 minutes, so recycling at 5 keeps you well within bounds.
For migrations, use Alembic with async support. Add a release_command to your azin.yaml to run migrations before the new version receives traffic:
services:
api:
build:
type: railpack
deploy:
release_command: "alembic upgrade head"
env:
ENVIRONMENT: production
PORT: "8000"If the migration fails, the deploy is cancelled and the previous version continues serving requests.
#Background tasks and workers
FastAPI offers three approaches to background work, depending on the task duration and reliability requirements.
For short, fire-and-forget tasks — use FastAPI's built-in BackgroundTasks. These run in the same process as your API after the response is sent:
from fastapi import BackgroundTasks
async def send_welcome_email(email: str):
...
@router.post("/users")
async def create_user(
user: UserCreate,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
):
new_user = User(**user.model_dump())
db.add(new_user)
await db.commit()
background_tasks.add_task(send_welcome_email, new_user.email)
return new_userThis works for lightweight tasks like sending emails or logging events. The task dies if the pod restarts — there's no retry mechanism.
For durable, retryable tasks — use a dedicated worker service with ARQ (async Redis queue). ARQ is the async-native option and pairs well with FastAPI:
# app/worker.py
from arq import cron
from arq.connections import RedisSettings
from app.config import settings
async def process_payment(ctx, payment_id: int):
...
async def daily_report(ctx):
...
class WorkerSettings:
functions = [process_payment]
cron_jobs = [cron(daily_report, hour=6, minute=0)]
redis_settings = RedisSettings.from_dsn(settings.redis_url)
max_jobs = 10
job_timeout = 300Enqueue jobs from your API routes:
from arq import create_pool
from arq.connections import RedisSettings
from app.config import settings
@router.post("/payments")
async def create_payment(payment: PaymentCreate, db: AsyncSession = Depends(get_db)):
...
redis = await create_pool(RedisSettings.from_dsn(settings.redis_url))
await redis.enqueue_job("process_payment", payment.id)
return {"status": "processing"}The worker service in your azin.yaml runs arq app.worker.WorkerSettings as a separate process. It shares REDIS_URL with your API service, so jobs flow through Memorystore in your GCP account.
#Why AZIN for FastAPI hosting
Your cloud, your data. FastAPI apps and PostgreSQL databases run in your own GCP account. You own the infrastructure, the billing relationship with Google, and the data. AZIN is the control plane — not the cloud provider. AWS and Azure are on our roadmap.
Native async support. FastAPI is ASGI-native, and Railpack configures Uvicorn automatically — no WSGI/Gunicorn mismatch. Other platforms default to Gunicorn for all Python apps, forcing you to manually configure Uvicorn workers. AZIN detects FastAPI and uses the right server out of the box.
Managed PostgreSQL and Redis. Cloud SQL and Memorystore are provisioned in your GCP account. Automated backups, connection pooling, and encryption at rest included. DATABASE_URL and REDIS_URL are injected at runtime — your FastAPI app reads them from the environment with no manual setup.
No cold starts for production traffic. GKE Autopilot keeps pods warm when min: 1 is set. The first GKE cluster is free — you pay only for the pods running your application. This differs from platforms where a managed Kubernetes cluster can cost ~$225/month in underlying cloud fees before any workloads run (based on typical AWS EKS configurations, as of February 2026).
Scale-to-zero staging. Deploy staging environments on lttle.cloud (in early access). When your staging FastAPI app receives no traffic, it scales to zero and costs nothing. Production stays warm on GKE; staging idles without burning compute.
Deploy Guides
Deploy Flask with AZIN
Flask's synchronous WSGI alternative. Same zero-config detection, managed Postgres, GCP BYOC.
#Related guides
- Deploy Docker containers with AZIN — For custom Dockerfile deployments
- Host PostgreSQL on AZIN — Managed Cloud SQL in your own GCP account
- Deploy Flask with AZIN — Flask's synchronous alternative with Gunicorn
- AZIN vs Railway — Python hosting platforms compared
- AZIN vs Render — FastAPI deployment: shared infra vs your own cloud
#Frequently asked questions
Deploy on private infrastructure
Managed AI environments with built-in isolation. Zero DevOps required.