Aller au contenu principal
FastAPIPythonDockerJWTFormation

Déployer FastAPI en production

30 min de lecture Apprendre FastAPI — Chapitre 6

Conteneurise ton API avec Docker, configure Nginx et Uvicorn, mets en place un CI/CD complet et applique les bonnes pratiques de production.

Dans le chapitre précédent, on a vu Tests et documentation automatique. Maintenant, ton API fonctionne en local. Il est temps de la mettre en production.

🎯 Objectif : Conteneuriser ton API FastAPI avec Docker, la placer derrière Nginx, automatiser le déploiement avec un pipeline CI/CD, et appliquer les bonnes pratiques qui font la différence entre un projet perso et une API de production.

Conteneuriser avec Docker

Une API qui tourne sur ta machine ne sert personne. Docker garantit que ton environnement est identique partout — du laptop au serveur de prod. Plus de “ça marche chez moi”.

Le Dockerfile optimisé

La clé : séparer l’installation des dépendances du code source. Docker met en cache chaque couche. Si seul ton code change, les dépendances ne sont pas réinstallées — le build passe de 2 minutes à 5 secondes.

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

💡 Nombre de workers : la formule classique est (2 × CPU) + 1. Sur un serveur 2 cores, mets 5 workers. En production sérieuse, Gunicorn pilote les workers Uvicorn — pense à Gunicorn comme le manager d’une équipe de serveurs dans un restaurant.

Docker Compose : l’orchestration complète

Un fichier docker-compose.yml assemble tous les services : API, base de données, reverse proxy. Un seul docker compose up -d et tout démarre dans le bon ordre.

services:
  api:
    build: .
    container_name: fastapi-app
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://fastapi:secret123@postgres:5432/app
      - SECRET_KEY=${SECRET_KEY}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: fastapi
      POSTGRES_PASSWORD: secret123
      POSTGRES_DB: app
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U fastapi"]
      interval: 5s
      retries: 5
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - api
    restart: unless-stopped

volumes:
  postgres_data:

🔥 Le healthcheck sur PostgreSQL est crucial. Sans lui, ton API démarre avant que la base soit prête et plante au premier appel SQL. Le depends_on avec condition: service_healthy résout ce problème classique que tout le monde rencontre.

Nginx comme reverse proxy

Nginx se place devant ton API pour gérer le SSL, le rate limiting et les headers de sécurité. Uvicorn n’est pas conçu pour être exposé directement à Internet — il manque de protections essentielles contre les attaques courantes.

upstream fastapi {
    server api:8000;
}

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

server {
    listen 443 ssl;
    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;

    add_header X-Frame-Options "DENY";
    add_header X-Content-Type-Options "nosniff";
    add_header Strict-Transport-Security "max-age=31536000";

    location / {
        proxy_pass http://fastapi;
        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;
    }

    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://fastapi;
    }
}

⚠️ Le rate limiting protège ton API des abus. 10 requêtes par seconde par IP avec un burst de 20, c’est un bon point de départ. Ajuste selon ton trafic réel. Les headers de sécurité (X-Frame-Options, HSTS) bloquent les attaques les plus courantes — clickjacking, downgrade HTTPS, injection de contenu.

Configuration et secrets

Ne hardcode jamais un mot de passe ou une clé API. Utilise Pydantic Settings pour charger les variables d’environnement de façon typée et validée. Si une variable manque, l’API refuse de démarrer — mieux vaut un crash immédiat qu’une faille silencieuse.

# config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    algorithm: str = "HS256"
    access_token_expire_minutes: int = 30
    env: str = "development"

    class Config:
        env_file = ".env"

settings = Settings()

Crée un fichier .env pour les valeurs locales, et un .env.example avec des valeurs fictives pour que l’équipe sache quoi configurer. Le .env ne va jamais dans Git — ajoute-le dans .gitignore dès la première minute.

💡 En production, les secrets viennent de variables d’environnement injectées par Docker, Kubernetes ou un vault (HashiCorp Vault, AWS Secrets Manager). Le fichier .env est uniquement pour le développement local.

Pipeline CI/CD avec GitHub Actions

L’automatisation élimine les erreurs humaines. À chaque push sur main, la pipeline teste, builde et déploie automatiquement. Un test qui échoue bloque tout — zéro compromis sur la qualité.

name: CI/CD FastAPI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test
        ports: ["5432:5432"]
        options: --health-cmd pg_isready --health-interval 10s --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt pytest httpx ruff
      - run: ruff check . && ruff format --check .
      - run: pytest --cov=. -v
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test
          SECRET_KEY: test-secret

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ secrets.DOCKER_USERNAME }}/fastapi-app:${{ github.sha }}
      - uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/fastapi-app
            docker compose pull && docker compose up -d

🎯 Le flux complet : push → lint avec ruff → tests pytest avec une vraie base PostgreSQL → build de l’image Docker → push sur le registry → déploiement SSH sur le serveur. Tout est automatique. Une PR qui casse les tests ne peut pas être mergée.

Pièges classiques et checklist

⚠️ Les erreurs qui coûtent cher en production :

  • uvicorn --reload en prod — Le hot-reload consomme des ressources et provoque des comportements imprévisibles sous charge. C’est un outil de développement, point.
  • allow_origins=["*"] dans CORS — Ça ouvre ton API à n’importe quel site web. Liste explicitement les domaines autorisés.
  • Pas de healthcheck — Sans endpoint /health, ton orchestrateur ne sait pas si ton API est vivante. Docker, Kubernetes, ton load balancer — tous en ont besoin.
  • Swagger exposé en production — La doc auto est une mine d’or pour les attaquants. Désactive-la ou protège-la derrière une authentification.
  • Base.metadata.create_all() en prod — Utilise Alembic pour versionner les migrations de schéma. Chaque modification est tracée, réversible, et appliquée automatiquement au déploiement.
  • Logs non structurés — En prod, utilise python-json-logger pour des logs JSON compatibles avec ELK ou Grafana Loki. Les print() ne scalent pas.

🎯 Checklist avant mise en prod :

  • ✅ Secrets dans .env ou un vault, jamais dans le code
  • ✅ HTTPS avec Let’s Encrypt configuré
  • ✅ Rate limiting activé sur Nginx
  • ✅ Migrations Alembic en place
  • ✅ Tests > 80% de couverture, pipeline CI verte
  • ✅ Logging structuré et centralisé
  • ✅ Backups PostgreSQL automatisés
  • .gitignore complet (.env, __pycache__, etc.)

Résumé

🔥 Ce qu’on a couvert : Docker pour conteneuriser l’API, Docker Compose pour orchestrer les services, Nginx comme reverse proxy avec SSL et rate limiting, Pydantic Settings pour la gestion des secrets, GitHub Actions pour le CI/CD automatique, et les pièges classiques de production à éviter absolument.

Avec cette série FastAPI en 6 chapitres, tu as tout pour construire une API professionnelle — des premiers endpoints jusqu’au déploiement automatisé. Le reste, c’est de la pratique : choisis un projet, code-le, déploie-le, itère. C’est comme ça qu’on progresse.

💡 À retenir : une API en production, c’est 20% de code métier et 80% d’infrastructure autour — tests, CI/CD, monitoring, sécurité. Ne néglige jamais cette partie. C’est elle qui fait la différence entre un prototype et un produit.

🖥️ Pratique sur ton propre serveur

Pour suivre Apprendre FastAPI en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.

Obtenir 200$

Articles liés