Docker et IA : Déployer un Modèle du Prototype à la Production

Docker et IA : Déployer un Modèle du Prototype à la Production

Votre modèle IA fonctionne parfaitement sur l’ordinateur de votre data scientist. Puis vient le moment fatidique du déploiement : dépendances manquantes, versions de bibliothèques incompatibles, “ça marchait sur ma machine”. Cette situation, que 73 % des équipes data ont vécu selon le rapport State of ML 2024 de Weights & Biases, a un remède connu et éprouvé : Docker. Conteneuriser un modèle IA n’est plus une compétence réservée aux DevOps — c’est une étape incontournable du cycle de vie de tout projet IA en production.

Pourquoi conteneuriser un modèle IA ?

Un modèle de machine learning n’est jamais seul : il dépend d’une version précise de Python, de PyTorch ou TensorFlow, de CUDA pour le GPU, de dizaines de bibliothèques avec leurs propres sous-dépendances. Sans isolation, ce “dependency hell” rend le déploiement cauchemardesque et la reproductibilité impossible.

Docker résout ce problème en encapsulant votre modèle et tout son environnement dans une image portable, qui s’exécute de manière identique sur votre laptop, un serveur cloud, ou un Raspberry Pi (avec les bons paramètres).

Avantages concrets pour une PME :

  • Déploiement reproductible en quelques minutes
  • Rollback instantané en cas de problème (revenir à l’image précédente)
  • Scalabilité horizontale facile (Kubernetes, Docker Swarm)
  • Isolation des dépendances entre projets IA
  • CI/CD automatisable

Dockerfile multi-stage pour un modèle Python/FastAPI

L’image Docker d’un modèle IA peut facilement dépasser 10 Go si elle est mal construite (PyTorch pèse 3+ Go). La stratégie multi-stage permet de séparer l’environnement de build de l’image de production finale.

Prenons un exemple concret : exposer un modèle de classification de texte via une API FastAPI.

# ===== STAGE 1 : Build =====
FROM python:3.11-slim AS builder

WORKDIR /app

# Installer les dépendances de build
RUN apt-get update && apt-get install -y 
    gcc 
    g++ 
    && rm -rf /var/lib/apt/lists/*

# Copier et installer les dépendances (couche mise en cache)
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# ===== STAGE 2 : Production =====
FROM python:3.11-slim AS production

WORKDIR /app

# Copier uniquement les packages installés depuis le builder
COPY --from=builder /root/.local /root/.local

# Copier le code de l'application
COPY app/ ./app/
COPY models/ ./models/

# Variables d'environnement
ENV PYTHONPATH=/app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH=/root/.local/bin:$PATH

# Utilisateur non-root pour la sécurité
RUN adduser --disabled-password --gecos '' appuser
USER appuser

EXPOSE 8000

# Health check intégré
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 
    CMD curl -f http://localhost:8000/health || exit 1

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

L’API FastAPI correspondante :

# app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from transformers import pipeline
import logging

logger = logging.getLogger(__name__)
app = FastAPI(title="Modèle Classification Texte", version="1.0.0")

# Charger le modèle au démarrage (une seule fois)
classifier = None

@app.on_event("startup")
async def load_model():
    global classifier
    logger.info("Chargement du modèle...")
    classifier = pipeline(
        "text-classification",
        model="./models/mon-modele-finetune",
        device=-1  # CPU (-1) ou GPU (0)
    )
    logger.info("Modèle chargé avec succès")

@app.get("/health")
async def health():
    return {"status": "ok", "model_loaded": classifier is not None}

class TextRequest(BaseModel):
    text: str
    
class PredictionResponse(BaseModel):
    label: str
    score: float

@app.post("/predict", response_model=PredictionResponse)
async def predict(request: TextRequest):
    if classifier is None:
        raise HTTPException(503, "Modèle non disponible")
    
    result = classifier(request.text)[0]
    return PredictionResponse(label=result["label"], score=result["score"])

Docker Compose : orchestrer le stack complet

En production, votre modèle IA ne tourne jamais seul. Il a besoin d’une base de données, d’un cache Redis pour les requêtes fréquentes, d’un reverse proxy. Docker Compose orchestre tout cela.

# docker-compose.yml
version: '3.8'

services:
  # API du modèle IA
  model-api:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    image: mon-modele-ia:1.0.0
    restart: unless-stopped
    environment:
      - MODEL_PATH=/app/models/mon-modele
      - REDIS_URL=redis://redis:6379
      - LOG_LEVEL=INFO
    volumes:
      # Monter les modèles depuis l'hôte (évite de les inclure dans l'image)
      - ./models:/app/models:ro
      - ./logs:/app/logs
    ports:
      - "8000:8000"
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    # Limites de ressources
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          memory: 2G

  # Cache pour les prédictions fréquentes
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - redis-data:/data

  # Reverse proxy (optionnel en dev, essentiel en prod)
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - model-api

volumes:
  redis-data:

Support GPU : YOLO comme exemple concret

Les modèles de vision par ordinateur comme YOLO nécessitent un GPU pour des performances acceptables. Voici comment configurer Docker avec support GPU.

Prérequis hôte : installer NVIDIA Container Toolkit.

# Installation NVIDIA Container Toolkit (Ubuntu)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor 
    -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | 
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | 
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker

Dockerfile pour YOLO avec GPU :

FROM pytorch/pytorch:2.2.0-cuda12.1-cudnn8-runtime

WORKDIR /app

RUN pip install --no-cache-dir 
    ultralytics==8.1.0 
    fastapi==0.110.0 
    uvicorn[standard]==0.27.0 
    python-multipart==0.0.9

COPY models/yolo11n.pt ./models/
COPY app/ ./app/

EXPOSE 8001
CMD ["uvicorn", "app.yolo_api:app", "--host", "0.0.0.0", "--port", "8001"]

docker-compose avec GPU :

services:
  yolo-api:
    build: .
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1        # ou "all" pour tous les GPU
              capabilities: [gpu]
    environment:
      - CUDA_VISIBLE_DEVICES=0
    volumes:
      - ./models:/app/models:ro

API de détection d’objets :

# app/yolo_api.py
from fastapi import FastAPI, UploadFile
from ultralytics import YOLO
import io
from PIL import Image

app = FastAPI()
model = YOLO("./models/yolo11n.pt")

@app.post("/detect")
async def detect(file: UploadFile):
    image_data = await file.read()
    image = Image.open(io.BytesIO(image_data))
    
    results = model(image, conf=0.5)
    
    detections = []
    for result in results:
        for box in result.boxes:
            detections.append({
                "class": result.names[int(box.cls)],
                "confidence": float(box.conf),
                "bbox": box.xyxy[0].tolist()
            })
    
    return {"detections": detections, "count": len(detections)}

Gestion des volumes et des modèles

Un anti-pattern courant : inclure les fichiers de modèle (qui pèsent souvent plusieurs Go) dans l’image Docker. Cela rend l’image lourde et le push/pull lent.

Bonne pratique : monter les modèles en volume

# Les modèles sont sur l'hôte ou dans un stockage partagé
volumes:
  - /data/models:/app/models:ro    # Read-only pour la sécurité
  - /data/logs:/app/logs           # Read-write pour les logs

Pour les environnements cloud (AWS EFS, Azure Files, GCP Filestore) :

volumes:
  models-efs:
    driver: local
    driver_opts:
      type: nfs
      o: addr=fs-xxxxx.efs.eu-west-3.amazonaws.com,nfsvers=4.1,rsize=1048576
      device: ":/"

CI/CD pour vos modèles IA

Un pipeline de déploiement continu pour un modèle IA doit inclure des étapes spécifiques :

# .github/workflows/deploy-model.yml
name: Build and Deploy Model API

on:
  push:
    branches: [main]
    paths: ['app/**', 'models/**', 'Dockerfile', 'requirements.txt']

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run model tests
        run: |
          pip install -r requirements-test.txt
          pytest tests/ -v --tb=short
          
  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Docker image
        run: |
          docker build -t mon-registre/model-api:${{ github.sha }} .
          docker tag mon-registre/model-api:${{ github.sha }}                      mon-registre/model-api:latest
      - name: Push to registry
        run: docker push mon-registre/model-api:${{ github.sha }}
        
  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: |
          ssh ${{ secrets.PROD_HOST }} "
            docker pull mon-registre/model-api:${{ github.sha }}
            docker-compose up -d --no-deps model-api
          "

Bonnes pratiques de sécurité

Ne jamais faire :

  • Inclure des clés API ou credentials dans l’image Docker
  • Faire tourner le conteneur en root (utilisez USER appuser)
  • Exposer directement le port du modèle sans reverse proxy

Toujours faire :

# Secrets via variables d'environnement (jamais en dur)
ENV API_KEY=${API_KEY}

# Utilisateur non-root
RUN addgroup --system appgroup && adduser --system --group appuser
USER appuser

# Image minimale
FROM python:3.11-slim  # pas python:3.11 (full)
# Lancer avec .env (jamais commité)
docker run --env-file .env mon-modele-ia:latest

Conclusion

Docker a transformé le déploiement de modèles IA d’une opération risquée et imprévisible en un processus reproductible et automatisable. Les patterns présentés ici — multi-stage build, Compose avec health checks, support GPU, volumes pour les modèles — couvrent la grande majorité des cas de déploiement en PME.

Le passage du prototype à la production est souvent l’étape qui fait échouer les projets IA. Investir dans une bonne infrastructure Docker dès le début du projet, c’est s’assurer que la valeur créée par votre modèle arrive réellement à vos utilisateurs.

Votre équipe a un modèle IA qui tourne en local et vous souhaitez le déployer en production ? Brio Novia accompagne les PME dans la mise en production de leurs modèles IA, de la conteneurisation au monitoring en production. Contactez-nous à contact@brio-novia.eu pour en discuter.