Client Area

FastAPI Production Deployment on DomainIndia VPS (Gunicorn + Uvicorn + nginx + PostgreSQL)

ByDomain India Team·DomainIndia Engineering
4 min readPublished 24 Apr 2026Updated 23 Jun 2026292 views

In this article

  • 1Why FastAPI
  • 2Stack we're deploying
  • 3Step 1 — Prepare VPS
  • 4Step 2 — Sample FastAPI app
  • 5Step 3 — Install + setup venv

FastAPI Production Deployment on DomainIndia VPS (Gunicorn + Uvicorn + nginx + PostgreSQL)

TL;DR
FastAPI is the dominant Python API framework of 2026 — async-first, type-safe, auto-OpenAPI docs. This guide deploys a production FastAPI app on DomainIndia VPS: Uvicorn workers under Gunicorn, systemd, nginx reverse proxy, PostgreSQL with SQLAlchemy, Alembic migrations, and zero-downtime reloads.

Why FastAPI

  • Async-first (handles 10K+ concurrent connections)
  • Pydantic for type-safe request/response models
  • Auto-generated OpenAPI/Swagger docs at /docs
  • 3-5× faster than Flask, same-league as Node.js Express
  • Massive ecosystem (SQLAlchemy 2, Alembic, Celery, APScheduler)

Good fit: REST APIs, WebSocket services, ML inference endpoints. Less ideal for server-rendered HTML apps — use Django or Flask for those.

Stack we're deploying

Client → nginx (443) → Gunicorn → 4× Uvicorn workers → FastAPI app → PostgreSQL
                                                                ↑
                                                          Redis (cache/queue)

Step 1 — Prepare VPS

bash
# AlmaLinux 9
sudo dnf install -y python3.12 python3.12-devel python3-pip nginx postgresql-server postgresql-contrib redis git certbot python3-certbot-nginx

# Ubuntu 22.04+
sudo apt install -y python3.12 python3.12-venv python3-pip nginx postgresql redis git certbot python3-certbot-nginx

# Create app user
sudo useradd -r -m -s /bin/bash fastapi
sudo su - fastapi

Step 2 — Sample FastAPI app

~fastapi/app/main.py:

python
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, EmailStr
import os

from .db import get_db, engine
from .models import User

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    print("Starting up")
    yield
    # Shutdown
    await engine.dispose()
    print("Shutting down")

app = FastAPI(
    title="MyAPI",
    version="1.0.0",
    lifespan=lifespan,
)

class UserCreate(BaseModel):
    email: EmailStr
    name: str

class UserOut(BaseModel):
    id: str
    email: str
    name: str
    class Config: from_attributes = True

@app.get("/health")
async def health():
    return {"status": "ok", "version": "1.0.0"}

@app.post("/users", response_model=UserOut)
async def create_user(payload: UserCreate, db: AsyncSession = Depends(get_db)):
    user = User(email=payload.email, name=payload.name)
    db.add(user)
    await db.commit()
    await db.refresh(user)
    return user

@app.get("/users/{user_id}", response_model=UserOut)
async def get_user(user_id: str, db: AsyncSession = Depends(get_db)):
    user = await db.get(User, user_id)
    if not user:
        raise HTTPException(404, "Not found")
    return user

~fastapi/app/db.py:

python
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
import os

DATABASE_URL = os.getenv("DATABASE_URL")  # postgresql+asyncpg://...

engine = create_async_engine(DATABASE_URL, pool_size=10, max_overflow=5)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)

async def get_db():
    async with SessionLocal() as session:
        yield session

~fastapi/app/models.py:

python
import uuid
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
    email: Mapped[str] = mapped_column(String(255), unique=True)
    name: Mapped[str] = mapped_column(String(100))

~fastapi/requirements.txt:

fastapi==0.114.0
uvicorn[standard]==0.30.6
gunicorn==22.0.0
sqlalchemy==2.0.35
asyncpg==0.29.0
alembic==1.13.2
pydantic[email]==2.8.2
python-dotenv==1.0.1

Step 3 — Install + setup venv

bash
cd ~fastapi
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Step 4 — PostgreSQL

bash
# As root
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql

sudo -u postgres psql <<EOF
CREATE DATABASE fastapi_prod;
CREATE USER fastapi WITH ENCRYPTED PASSWORD 'changeme';
GRANT ALL PRIVILEGES ON DATABASE fastapi_prod TO fastapi;
\c fastapi_prod
GRANT ALL ON SCHEMA public TO fastapi;
EOF

Step 5 — Alembic migrations

bash
cd ~fastapi
alembic init -t async alembic

Edit alembic/env.py to import your models + use DATABASE_URL from env.

python
from app.models import Base
from app.db import DATABASE_URL
config.set_main_option("sqlalchemy.url", DATABASE_URL)
target_metadata = Base.metadata

Create migration:

bash
alembic revision --autogenerate -m "create users"
alembic upgrade head

Step 6 — Gunicorn + Uvicorn workers

~fastapi/gunicorn.conf.py:

python
import multiprocessing

bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000

timeout = 60
keepalive = 5
graceful_timeout = 30

accesslog = "-"
errorlog = "-"
loglevel = "info"

preload_app = True   # fork after load — faster reloads

Test:

bash
cd ~fastapi
export DATABASE_URL=postgresql+asyncpg://fastapi:changeme@localhost/fastapi_prod
.venv/bin/gunicorn -c gunicorn.conf.py app.main:app

Visit http://localhost:8000/docs — Swagger UI shows your API.

Step 7 — systemd service

/etc/systemd/system/fastapi.service:

ini
[Unit]
Description=FastAPI Application
After=network.target postgresql.service redis.service

[Service]
Type=notify
User=fastapi
Group=fastapi
WorkingDirectory=/home/fastapi
ExecStart=/home/fastapi/.venv/bin/gunicorn -c /home/fastapi/gunicorn.conf.py app.main:app
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30
Restart=on-failure
RestartSec=5

EnvironmentFile=/home/fastapi/.env

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/fastapi

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

~fastapi/.env:

DATABASE_URL=postgresql+asyncpg://fastapi:changeme@localhost/fastapi_prod
REDIS_URL=redis://localhost:6379/0
JWT_SECRET=random-long-string
ENV=production

Start:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi
sudo journalctl -u fastapi -f

Step 8 — nginx reverse proxy + SSL

/etc/nginx/conf.d/fastapi.conf:

nginx
upstream fastapi {
    server 127.0.0.1:8000 fail_timeout=0;
    keepalive 32;
}

server {
    listen 80;
    server_name api.yourcompany.com;

    client_max_body_size 20M;

    location / {
        proxy_pass http://fastapi;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";

        proxy_read_timeout 60s;
        proxy_buffering off;    # better for streaming responses
    }
}
bash
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.yourcompany.com

Step 9 — Zero-downtime deploys

Gunicorn supports SIGHUP to gracefully reload workers without dropping connections:

bash
# Deploy hook
cd /home/fastapi
git pull
.venv/bin/pip install -r requirements.txt
.venv/bin/alembic upgrade head
sudo systemctl reload fastapi   # sends SIGHUP via ExecReload

Workers restart one at a time; in-flight requests finish cleanly.

Step 10 — Observability

Add Prometheus metrics:

bash
pip install prometheus-fastapi-instrumentator
python
from prometheus_fastapi_instrumentator import Instrumentator

app = FastAPI(...)
Instrumentator().instrument(app).expose(app)
# Now /metrics endpoint is live

Scrape with Prometheus (see our Observability guide).

Background tasks — which tool?

ToolBest forComplexity
FastAPI BackgroundTasksShort tasks triggered by requestZero — built-in
APSchedulerCron-like schedules in-processLow
Celery + RedisHeavy async work, multiple workersMedium
ArqLightweight async task queueLow

Start with BackgroundTasks for simple needs; scale to Celery or Arq when you need retries + multiple worker machines.

Common pitfalls

FAQ

Q Flask, FastAPI, or Django?

FastAPI for modern APIs (async, typed, OpenAPI). Django for full-stack with templates, admin, ORM. Flask if you're maintaining existing Flask apps.

Q Uvicorn alone or Gunicorn + Uvicorn?

Uvicorn alone works for dev + light prod. Gunicorn adds process management, graceful reload, better logging — use it for production.

Q Can I run this on DomainIndia shared hosting?

Shared cPanel's Setup Python App runs simple FastAPI apps (via Passenger). For async + multiple workers, use VPS.

Q Asyncpg or psycopg3?

Asyncpg is faster but less feature-complete. Psycopg3 (async mode) is more versatile, supports synchronous adapters. For greenfield FastAPI: asyncpg.

Q How many concurrent requests on a 2 GB VPS?

With 4 Uvicorn workers × 1000 connections each = 4K theoretical. Real-world with DB-backed API: 500-2000/sec. Bottleneck is usually Postgres.

FastAPI in production wants a solid VPS with PostgreSQL. Order VPS

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket