Guide de déploiement

Déployer FastAPI sur un VPS : guide complet production

Déployer sur un VPS Cloud →

Tutoriel

Déployer FastAPI sur un VPS : guide complet production

Développement10 min de lecture7 étapes

FastAPI s'est imposé comme le framework Python de choix pour les API rapides et typées, grâce à son support natif de l'asynchrone et de la documentation OpenAPI. Le déployer sur votre propre VPS vous donne la main sur le nombre de workers, le WebSocket et la latence, sans plafond imposé par un service managé. Ce guide couvre l'intégralité de la mise en production : conteneurisation multi-stage, base de données avec migrations Alembic, CI/CD automatisé et dépannage des erreurs fréquentes.

Sommaire· Pourquoi self-héberger FastAPI sur un VPS ?1/9
  1. 01Pourquoi self-héberger FastAPI sur un VPS ?
  2. 02Bénéfices concrets d'un FastAPI auto-hébergé
  3. 03Prérequis matériels, logiciels et versions
  4. 04Déployer FastAPI étape par étape
  5. 05Base de données et migrations
  6. 06CI/CD et déploiements automatisés
  7. 07Monitoring et logs
  8. 08Dépannage des erreurs fréquentes
  9. 09Déployer FastAPI en un clic depuis le Marketplace ServOrbit

Pourquoi self-héberger FastAPI sur un VPS ?

FastAPI tire toute sa performance du modèle ASGI asynchrone : connexions WebSocket persistantes, streaming de réponses et appels concurrents vers d'autres services. Les plateformes serverless coupent souvent les connexions longues et facturent chaque requête, ce qui pénalise une API à fort débit ou temps réel. Sur un VPS, vous lancez Uvicorn piloté par Gunicorn avec autant de workers que de cœurs, vous gardez les connexions ouvertes aussi longtemps que nécessaire et vous placez Nginx en frontal pour le TLS et le buffering. Vous contrôlez aussi le pool de connexions vers PostgreSQL et le cache Redis, deux leviers essentiels quand la charge monte — le débit réellement tenu se mesure sur votre propre trafic.

Bénéfices concrets d'un FastAPI auto-hébergé

  • WebSockets et Server-Sent Events sans coupure de connexion ni timeout imposé.
  • Réglage fin des workers Uvicorn/Gunicorn selon le nombre de cœurs du VPS.
  • Aucun démarrage à froid (cold start) : le processus reste debout entre deux requêtes.
  • Documentation Swagger et ReDoc servies en interne, accessibles ou protégées à votre guise.
  • Intégration directe avec un pool asynchrone PostgreSQL (asyncpg) et Redis.
  • Coût fixe quel que soit le nombre d'appels API, idéal pour un backend de production.

Prérequis matériels, logiciels et versions

Une API FastAPI est légère : 1 vCPU et 1 Go de RAM suffisent pour démarrer, mais visez 2 vCPU et 2 Go dès que vous ajoutez une base de données et un cache sur le même VPS. Pour une charge soutenue (>100 req/s), comptez 4 vCPU et 4 Go minimum.

Versions recommandées en 2025 : Python 3.11+ (3.12 pour les performances maximales), FastAPI 0.115+ (support complet de Pydantic v2 et des annotations Python 3.10+), Uvicorn 0.30+ (correctifs de stabilité event loop). Installez via Docker avec l'image python:3.12-slim pour un démarrage rapide, ou depuis le dépôt officiel avec pip install fastapi[standard]>=0.115 uvicorn[standard]>=0.30 gunicorn>=22.0.

Du côté infrastructure : Docker Engine 24+, Docker Compose v2 (plugin intégré, docker compose), Nginx 1.24+ en reverse proxy, et un domaine pointé vers le VPS pour le certificat TLS. Ubuntu 22.04/24.04 LTS est la base recommandée ; Debian 12 fonctionne également.

Déployer FastAPI étape par étape

  1. Initialiser le VPS et l'application

    Connectez-vous en SSH, installez Docker, clonez votre dépôt et créez un .env depuis le modèle .env.example fourni avec votre projet. Ce fichier doit contenir au minimum :

    DATABASE_URL=postgresql+asyncpg://user:password@db:5432/appdb
    REDIS_URL=redis://redis:6379/0
    SECRET_KEY=changeme-in-production
    CORS_ORIGINS=["https://yourdomain.com"]
    ENVIRONMENT=production

    Vérifiez que votre point d'entrée expose bien app = FastAPI() et que l'application charge sa configuration depuis les variables d'environnement (jamais en dur).

  2. Conteneuriser avec un Dockerfile multi-stage

    Le multi-stage sépare l'étape de build (compilation des dépendances, génération de wheels) de l'image de runtime, ce qui réduit la taille finale de 60 à 80 % et élimine les outils de compilation de la surface d'attaque :

    # ── Stage 1 : build ──────────────────────────────────────
    FROM python:3.12-slim AS builder
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --upgrade pip && \
        pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
    
    # ── Stage 2 : runtime ────────────────────────────────────
    FROM python:3.12-slim
    WORKDIR /app
    COPY --from=builder /wheels /wheels
    RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
    COPY . .
    EXPOSE 8000
    CMD ["gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker",
         "--bind", "0.0.0.0:8000", "--workers", "4",
         "--timeout", "120", "--keep-alive", "5"]

    Ajoutez un endpoint /health dans votre application pour les healthchecks :

    @app.get("/health")
    async def health():
        return {"status": "ok"}
  3. Régler les workers Gunicorn de façon optimale

    La formule (2 × cœurs) + 1 est le point de départ pour des workers CPU-bound. FastAPI étant ASGI/asynchrone, chaque worker gère plusieurs requêtes en concurrence via l'event loop — la règle devient plutôt cœurs + 1 à 2 × cœurs pour des charges I/O-bound classiques (appels DB, HTTP externes).

    Passez la valeur via une variable d'environnement pour l'ajuster sans reconstruire l'image :

    WEB_CONCURRENCY=4  # dans .env ou docker-compose.yml
    CMD ["gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker",
         "--bind", "0.0.0.0:8000",
         "--workers", "${WEB_CONCURRENCY:-4}"]

    Sous forte charge, surveillez l'utilisation CPU par worker (docker stats) et le nombre de requêtes en attente dans les logs Gunicorn (--access-logfile -) avant d'augmenter le nombre de workers.

  4. Orchestrer les services avec Docker Compose

    Dans docker-compose.yml, déclarez le service api, un service db PostgreSQL et un service redis. Utilisez depends_on avec condition: service_healthy pour que l'API attende que la base soit prête avant de démarrer :

    services:
      api:
        build: .
        env_file: .env
        ports: ["8000:8000"]
        depends_on:
          db:
            condition: service_healthy
          redis:
            condition: service_started
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
          interval: 30s
          retries: 3
    
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_USER: user
          POSTGRES_PASSWORD: password
          POSTGRES_DB: appdb
        volumes: ["pgdata:/var/lib/postgresql/data"]
        healthcheck:
          test: ["CMD", "pg_isready", "-U", "user"]
          interval: 10s
          retries: 5
    
      redis:
        image: redis:7-alpine
    
    volumes:
      pgdata:
  5. Placer Nginx en reverse proxy

    Configurez un bloc serveur avec proxy_pass http://api:8000 et, pour les WebSockets, ajoutez les headers nécessaires. Désactivez le buffering pour le streaming si nécessaire :

    server {
        listen 443 ssl;
        server_name api.yourdomain.com;
    
        location / {
            proxy_pass http://api:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_buffering off;  # pour le streaming
        }
    }
  6. Sécuriser avec un certificat TLS

    Obtenez un certificat Let's Encrypt via Certbot ou un conteneur companion, forcez le HTTPS et redirigez le HTTP. Pensez à restreindre l'accès à /docs et /openapi.json en production si l'API n'est pas publique.

  7. Lancer et tester

    Démarrez avec docker compose up -d, vérifiez la santé via le endpoint /health que vous aurez ajouté, et testez la documentation interactive. Surveillez les logs Uvicorn pour ajuster le nombre de workers sous charge réelle.

Pour les tâches longues (envoi d'emails, traitement d'images, appels externes lents), ne bloquez pas l'event loop : déportez-les vers un worker en arrière-plan avec BackgroundTasks pour les cas simples, ou vers Celery/ARQ avec Redis pour les traitements lourds. Une API qui répond vite et délègue le travail asynchrone reste réactive même sous forte concurrence.

Base de données et migrations

Pour les applications FastAPI en production, asyncpg combiné à SQLAlchemy 2.x (mode async) offre un excellent équilibre entre performances et maintenabilité. Configurez le pool de connexions en fonction de la charge attendue :

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

engine = create_async_engine(
    DATABASE_URL,
    pool_size=10,        # connexions persistantes
    max_overflow=20,     # connexions supplémentaires sous pic
    pool_timeout=30,     # secondes avant erreur
    pool_recycle=1800,   # recycle après 30 min pour éviter les timeouts PostgreSQL
    echo=False,
)

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

Injectez la session dans vos routes via une dépendance FastAPI :

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

Pour les migrations de schéma, Alembic est l'outil standard. Initialisez-le dans votre projet (alembic init migrations) puis configurez alembic.ini pour pointer sur votre DATABASE_URL. Les commandes essentielles :

# Générer une migration depuis vos modèles SQLAlchemy
alembic revision --autogenerate -m "add_users_table"

# Appliquer les migrations en attente
alembic upgrade head

# Vérifier l'état courant
alembic current

En production, exécutez alembic upgrade head avant de démarrer l'API — ajoutez-le comme commande d'init dans votre docker-compose.yml ou dans un script de démarrage séparé. Ne lancez jamais les migrations depuis plusieurs instances simultanément.

CI/CD et déploiements automatisés

Un pipeline GitHub Actions simple suffit pour la majorité des projets FastAPI : build de l'image, push vers un registry, et déploiement sur le VPS par SSH avec zero-downtime.

# .github/workflows/deploy.yml
name: Deploy FastAPI
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build & push Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
          echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: deploy
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            cd /opt/myapp
            export IMAGE_TAG=${{ github.sha }}
            docker compose pull api
            docker compose up -d --no-deps api
            docker compose exec api alembic upgrade head

L'option --no-deps sur docker compose up redémarre uniquement le conteneur api sans toucher db ni redis, ce qui garantit un zero-downtime pour les services tiers. Docker Compose démarre le nouveau conteneur avant d'arrêter l'ancien (rolling update interne).

Pour des déploiements plus robustes avec rollback instantané, envisagez de tagger vos images avec le SHA du commit et de conserver les 3 dernières versions localement — docker image prune avec --filter until=72h nettoie les anciennes images automatiquement.

Monitoring et logs

Accédez aux logs Uvicorn directement via Docker :

# Logs en temps réel
docker compose logs -f api

# 100 dernières lignes
docker compose logs --tail=100 api

Pour des logs structurés en JSON (indexables par un agrégateur comme Loki ou Elasticsearch), remplacez le logger Python standard par loguru ou structlog :

from loguru import logger
import sys

logger.remove()
logger.add(sys.stdout, format="{time} {level} {message}", serialize=True)

@app.middleware("http")
async def log_requests(request, call_next):
    logger.info("request", method=request.method, url=str(request.url))
    response = await call_next(request)
    logger.info("response", status=response.status_code)
    return response

Pour exposer des métriques Prometheus, ajoutez un endpoint /metrics basique avec prometheus-fastapi-instrumentator :

from prometheus_fastapi_instrumentator import Instrumentator

Instrumentator().instrument(app).expose(app, endpoint="/metrics")

Restrictez ensuite /metrics à votre réseau interne depuis Nginx (directive allow 127.0.0.1; deny all;) pour éviter d'exposer les métriques publiquement.

Dépannage des erreurs fréquentes

Worker timed out (pid XXXXX) — Gunicorn tue un worker qui n'a pas répondu dans le délai --timeout. Causes fréquentes : une route synchrone (def au lieu de async def) bloque l'event loop, ou une opération bloquante (lecture fichier, appel réseau) s'exécute sans asyncio.run_in_executor. Augmenter --timeout masque le problème ; la bonne correction est de rendre la route asynchrone ou de déléguer le travail bloquant.

Address already in use (port 8000) — Un processus Gunicorn précédent est encore actif. En Docker : docker compose down puis docker compose up -d force la recréation du conteneur et libère le port. Vérifiez avec docker ps -a qu'aucun conteneur zombie ne tourne.

502 Bad Gateway après un déploiement — Nginx a transmis la requête à l'API pendant son redémarrage. Attendez 10-15 secondes que le healthcheck Docker confirme que le nouveau conteneur est healthy avant de considérer le déploiement terminé. Ajoutez proxy_read_timeout 60; dans Nginx pour absorber les démarrages lents.

CORS en production : No 'Access-Control-Allow-Origin' header — FastAPI sert les origines déclarées dans CORSMiddleware. Vérifiez que CORS_ORIGINS dans votre .env contient exactement l'URL du frontend (protocole + domaine + port si non standard, sans slash final). Une valeur ["*"] fonctionne en développement mais ne doit jamais partir en production.

Connexion DB refusée au démarrage — L'API démarre avant que PostgreSQL soit prêt. Le depends_on avec condition: service_healthy dans Docker Compose résout ce cas. En Kubernetes ou sur des déploiements sans Compose, implémentez une boucle de retry avec backoff exponentiel dans le code de démarrage de l'application.

Déployer FastAPI en un clic depuis le Marketplace ServOrbit

Le Marketplace ServOrbit propose un template FastAPI Stack préconfiguré : Python 3.12, Uvicorn, Gunicorn, Nginx et PostgreSQL 16 sont installés automatiquement sur votre VPS dès la commande. Pas de session SSH pour l'installation de base — vous arrivez directement à l'étape de cloner votre dépôt et de lancer Uvicorn. La fiche marketplace contient les spécifications exactes, les cas d'usage et un guide de première connexion.

Déployez votre FastAPI Stack en quelques minutes

Le template FastAPI Stack ServOrbit installe Python 3.12, Uvicorn, Gunicorn, Nginx et PostgreSQL 16 sur votre VPS. Lancez votre API immédiatement.

Besoin d'aide ?

Parcourez notre centre d'aide et notre FAQ, ou contactez notre équipe — rappel, WhatsApp ou e-mail. Support en français, anglais et arabe.

Écrire sur WhatsApps'ouvre dans un nouvel onglet