Menu
Szybki wybór
Hosting Domeny VPS SSL Kalkulator Porównania FAQ
Aktywne kody
Wszystkie kody rabatowe

FastAPI — deployment produkcyjny na VPS z Uvicorn i Nginx

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS

FastAPI to najszybszy framework webowy dla Pythona — async z natury, z automatyczną dokumentacją OpenAPI i walidacją przez Pydantic. W benchmarkach wyprzedza Flask nawet 5-krotnie przy operacjach I/O. Jednak wdrożenie na VPS wymaga kilku kroków: Uvicorn (serwer ASGI), Gunicorn (process manager), Nginx (reverse proxy) i systemd (autostart). Ten artykuł przeprowadza cię przez cały proces od instalacji do działającego API w produkcji, z async SQLAlchemy i JWT authentication.

Instalacja i środowisko wirtualne

# Ubuntu 22.04 / 24.04 — przygotowanie srodowiska
sudo apt update && sudo apt install -y python3.11 python3.11-venv python3-pip

# Konto aplikacji (bez sudo, bez logowania)
sudo useradd -m -s /bin/bash fastapi
sudo su - fastapi

# Venv i zalenosci
python3.11 -m venv /home/fastapi/venv
source /home/fastapi/venv/bin/activate

pip install fastapi "uvicorn[standard]" gunicorn \
    sqlalchemy[asyncio] asyncpg alembic \
    python-jose[cryptography] passlib[bcrypt] \
    pydantic-settings python-multipart

# Struktura projektu
mkdir -p /home/fastapi/app/{routers,models,schemas,core,db}
cd /home/fastapi/app

Struktura aplikacji FastAPI

Dobra architektura FastAPI opiera się na routerach (osobne pliki per feature), zależnościach (Depends) i schematach Pydantic oddzielonych od modeli ORM. Poniżej minimalna, ale kompletna struktura z async endpointami i przykładowym routerem.

# main.py — entry point
from fastapi import FastAPI
from contextlib import asynccontextmanager
from app.db.session import engine
from app.models import Base
from app.routers import users, items

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: mozna tutaj inicjalizowac polaczenia
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    # Shutdown: cleanup
    await engine.dispose()

app = FastAPI(
    title="Moje API",
    version="1.0.0",
    lifespan=lifespan,
)

app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(items.router, prefix="/items", tags=["items"])

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

# routers/users.py — przykladowy router z async
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db
from app import crud, schemas

router = APIRouter()

@router.get("/", response_model=list[schemas.User])
async def read_users(
    skip: int = 0,
    limit: int = 100,
    db: AsyncSession = Depends(get_db),
):
    users = await crud.get_users(db, skip=skip, limit=limit)
    return users

@router.post("/", response_model=schemas.User, status_code=201)
async def create_user(
    user: schemas.UserCreate,
    db: AsyncSession = Depends(get_db),
):
    db_user = await crud.get_user_by_email(db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    return await crud.create_user(db=db, user=user)

SQLAlchemy async ORM + asyncpg

# db/session.py — async engine i session
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from app.core.config import settings

engine = create_async_engine(
    settings.DATABASE_URL,  # postgresql+asyncpg://user:pass@localhost/db
    pool_size=10,
    max_overflow=20,
    pool_pre_ping=True,
    echo=False,  # True w dev, False w prod
)

AsyncSessionLocal = async_sessionmaker(
    engine,
    class_=AsyncSession,
    expire_on_commit=False,
)

async def get_db():
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

# models/user.py — model ORM
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from app.db.base_class import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, index=True, nullable=False)
    hashed_password = Column(String, nullable=False)
    is_active = Column(Boolean, default=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

# crud/users.py — operacje async na bazie
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.user import User
from app.schemas.user import UserCreate
from app.core.security import get_password_hash

async def get_user_by_email(db: AsyncSession, email: str):
    result = await db.execute(select(User).where(User.email == email))
    return result.scalar_one_or_none()

async def create_user(db: AsyncSession, user: UserCreate):
    hashed = get_password_hash(user.password)
    db_user = User(email=user.email, hashed_password=hashed)
    db.add(db_user)
    await db.flush()
    await db.refresh(db_user)
    return db_user

Gunicorn + UvicornWorker — konfiguracja produkcyjna

Uvicorn sam w sobie nie potrafi zarządzać wieloma procesami — do tego służy Gunicorn z workerem UvicornWorker. Gunicorn obsługuje sygnały (SIGTERM, SIGHUP), graceful shutdown i auto-restart przy awarii. Plik konfiguracyjny gunicorn.conf.py jest czytelniejszy niż długie flagi w CLI.

# /home/fastapi/app/gunicorn.conf.py
bind = "unix:/run/fastapi/fastapi.sock"
workers = 4          # 2 * CPU + 1 dla I/O-bound
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
timeout = 30
keepalive = 5
max_requests = 1000  # restart workera po N requestach (zapobiega memory leaks)
max_requests_jitter = 100
preload_app = True   # wspolny memory dla forked workers
accesslog = "/home/fastapi/logs/access.log"
errorlog = "/home/fastapi/logs/error.log"
loglevel = "info"
proc_name = "fastapi-app"

# Test uruchomienia (z katalogu aplikacji):
# source /home/fastapi/venv/bin/activate
# gunicorn main:app -c gunicorn.conf.py

Systemd service — autostart i restart

# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI Gunicorn
After=network.target postgresql.service
Wants=postgresql.service

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

[Install]
WantedBy=multi-user.target

# Wlaczenie i start
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi
sudo systemctl status fastapi

# Zero-downtime reload (wymusza nowe workery bez przerwy)
sudo systemctl reload fastapi

# Logi
journalctl -u fastapi -f

Nginx reverse proxy z rate limiting

# /etc/nginx/sites-available/fastapi
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;

upstream fastapi_backend {
    server unix:/run/fastapi/fastapi.sock fail_timeout=0;
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Nagłówki bezpieczenstwa
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header Referrer-Policy strict-origin-when-cross-origin;

    location / {
        limit_req zone=api burst=50 nodelay;

        proxy_pass http://fastapi_backend;
        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_connect_timeout 5s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
        proxy_buffering off;
    }

    location /docs {
        # Ogranicz dostep do dokumentacji w prod
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        deny all;
        proxy_pass http://fastapi_backend;
    }
}

# Test i przeladowanie Nginx
sudo nginx -t && sudo systemctl reload nginx

JWT Authentication — wzorzec produkcyjny

# core/security.py
from datetime import datetime, timedelta, timezone
from jose import jwt, JWTError
from passlib.context import CryptContext
from app.core.config import settings

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")

# core/deps.py — dependency injection
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from jose import JWTError, jwt
from app.core.config import settings
from app.db.session import get_db
from app import crud

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db),
):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
        user_id: int = payload.get("sub")
        if user_id is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = await crud.get_user(db, user_id=int(user_id))
    if user is None or not user.is_active:
        raise credentials_exception
    return user

Porównanie FastAPI vs Flask vs Django

Cecha FastAPI Flask Django REST
Wydajność (req/s) ~50 000 (async) ~8 000 (sync) ~6 000 (sync)
Async / ASGI Natywny async Opcjonalny (gevent) ASGI od Django 3.1
Walidacja danych Pydantic v2 (built-in) Marshmallow / WTForms DRF Serializers
Dokumentacja API OpenAPI auto (Swagger) Flasgger (ręcznie) drf-spectacular
Krzywa nauki Niska–Średnia Niska Wysoka (ekosystem)
Wbudowane funkcje Tylko API Tylko API ORM, admin, auth
Typowe użycie REST API, ML serving Proste API, mikrousługi Full-stack, CMS

Najczęstsze pytania

Czy FastAPI działa na hostingu współdzielonym? +
FastAPI wymaga serwera ASGI (Uvicorn lub Hypercorn) i długo działającego procesu — tego hosting współdzielony nie obsługuje. Do FastAPI potrzebujesz VPS lub serwisu PaaS (Railway, Render, Fly.io). Najtańsze opcje w Polsce to Mikrus (VPS od ~15 PLN/msc) i Contabo (VPS od ~20 PLN/msc).
Uvicorn vs Gunicorn — co wybrać do produkcji? +
W produkcji używamy Gunicorn jako process managera z workerami UvicornWorker. Gunicorn zarządza procesami (auto-restart, graceful reload, resource limits), a Uvicorn obsługuje ASGI. Typowo: gunicorn -k uvicorn.workers.UvicornWorker -w 4 main:app. Liczba workerów = 2 × CPU + 1, choć dla I/O-bound aplikacji możesz użyć więcej.
FastAPI vs Flask vs Django — która jest najszybsza? +
FastAPI jest znacznie szybszy od Flask i Django przy operacjach I/O, dzięki async/await i ASGI. W benchmarkach FastAPI obsługuje 3-5x więcej żądań na sekundę niż Flask (WSGI, synchroniczny). Django REST Framework jest wolniejszy. FastAPI wygrywa też pod kątem automatycznej dokumentacji (OpenAPI/Swagger) i walidacji przez Pydantic. Flask jest prostszy, Django ma więcej wbudowanych funkcji (ORM, admin).
Jak połączyć FastAPI z bazą danych PostgreSQL? +
Używaj SQLAlchemy w trybie async z asyncpg jako sterownikiem. Wzorzec: create_async_engine("postgresql+asyncpg://user:pass@localhost/db"), AsyncSession z sessionmaker, dependency injection przez Depends(get_db). Nie używaj synchronicznego SQLAlchemy z FastAPI — blokuje event loop i niweluje korzyści async.

Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.