
Un serveur MCP qui tourne dans un terminal n'est pas un service. Le mettre en production, c'est répondre à des questions que le mode python server.py ignore : comment l'exposer en TLS, comment faire cohabiter plusieurs serveurs sur une même machine, comment savoir s'il est en bonne santé, comment le redémarrer sans interruption. Ce guide assemble une stack de déploiement complète : trois serveurs MCP conteneurisés derrière une passerelle Traefik qui termine le TLS, route par nom d'hôte, redirige le trafic HTTP, surveille la santé de chaque serveur et limite le débit. Le tout en Docker Compose, reproductible à l'identique. Public visé : développeur qui a écrit un serveur MCP HTTP et veut le déployer proprement.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Choisir entre self-hosting et SaaS pour héberger un serveur MCP.
- Conteneuriser un serveur MCP avec une image reproductible.
- Placer plusieurs serveurs derrière une passerelle Traefik : routage par hôte, TLS, redirection HTTP.
- Ajouter des health checks et une limitation de débit.
- Exploiter la stack : logs, sauvegardes, versions.
Prérequis
Section intitulée « Prérequis »Ce guide est l'aboutissement de la série MCP. Il suppose acquis :
- Un serveur MCP en transport Streamable HTTP — voir Transports MCP et Serveur MCP avancé pour le packaging.
- Docker et Docker Compose.
- Des notions de reverse proxy — sinon, la formation Traefik pose les bases.
Le code complet de ce guide est dans le lab lab-ia-mcp/mcp/deploiement.
Self-hosting ou SaaS : le choix d'hébergement
Section intitulée « Self-hosting ou SaaS : le choix d'hébergement »Avant toute architecture, une décision : où le serveur MCP va-t-il tourner ? Deux voies existent, et elles n'engagent pas les mêmes responsabilités.
L'option SaaS — héberger le serveur sur une plateforme managée — décharge de l'exploitation : pas de TLS à gérer, pas de mise à jour d'OS. En contrepartie, un serveur MCP agit sur vos systèmes : le confier à un tiers, c'est lui donner ce périmètre d'action. Pour un serveur qui touche à des données internes — fichiers, bases, API privées — cette externalisation est rarement acceptable.
L'option self-hosting garde le serveur sur votre infrastructure. C'est le choix par défaut dès que le serveur manipule autre chose que des données publiques, et c'est le parti pris de ce guide. La contrepartie est réelle : le TLS, les health checks, les logs, les sauvegardes deviennent votre travail. La bonne nouvelle, c'est qu'une passerelle bien choisie absorbe l'essentiel de cette charge.
L'architecture : une passerelle devant les serveurs
Section intitulée « L'architecture : une passerelle devant les serveurs »Exposer chaque serveur MCP directement sur un port public ne tient pas : chacun devrait gérer son propre certificat, son propre durcissement, sa propre limitation de débit. La réponse standard est une passerelle — un point d'entrée unique.
Dans cette stack, Traefik est cette passerelle. Il écoute en façade sur les ports 80 et 443, et il est le seul composant exposé. Derrière lui, trois serveurs MCP — notes, meteo, recherche — tournent dans des conteneurs sur un réseau Docker interne, sans port publié. Traefik reçoit chaque requête, termine le TLS, identifie le serveur cible d'après le nom d'hôte demandé, et relaie en HTTP simple vers le bon conteneur.
Ce découpage concentre toute la sécurité réseau en un point. Les serveurs MCP, eux, ne parlent qu'un HTTP interne et ne connaissent rien au TLS — ce qui les rend plus simples et plus faciles à tester.
Conteneuriser le serveur MCP
Section intitulée « Conteneuriser le serveur MCP »Un déploiement reproductible commence par une image déterministe. Le Dockerfile ci-dessous construit l'image du serveur MCP. Deux détails comptent autant que le reste : la base est épinglée par digest — pas seulement par tag — et le serveur tourne en utilisateur non-root.
FROM python:3.12-slim@sha256:9d3abd9fc11d06998ccdbdd93b4dd49b5ad7d67fcbbc11c016eb0eb2c2194891
WORKDIR /app
# --only-binary : pas d'exécution de script de build à l'installation.# Version épinglée : l'image doit être reproductible à l'identique.RUN pip install --no-cache-dir --only-binary=:all: mcp==1.27.1
COPY mcp_server.py .
# Compte non-root : le serveur ne tourne jamais en root.RUN useradd --create-home --uid 10001 mcpUSER mcp
EXPOSE 8000CMD ["python", "mcp_server.py"]Le serveur lui-même est un FastMCP en transport Streamable HTTP, paramétré par une variable d'environnement pour que la même image serve les trois rôles. Il écoute sur 0.0.0.0 — indispensable pour être joignable depuis Traefik — et expose, en plus de l'endpoint MCP, une route /healthz.
import osfrom starlette.responses import JSONResponsefrom mcp.server.fastmcp import FastMCP
MCP_NAME = os.environ.get("MCP_NAME", "mcp")mcp = FastMCP(MCP_NAME, host="0.0.0.0", port=8000)
@mcp.custom_route("/healthz", methods=["GET"])async def healthz(request) -> JSONResponse: """Sonde de santé : 200 tant que le serveur répond.""" return JSONResponse({"status": "ok", "server": MCP_NAME})La stack Docker Compose : Traefik et trois serveurs
Section intitulée « La stack Docker Compose : Traefik et trois serveurs »Le fichier compose.yml décrit la stack. Le service traefik est le seul à publier des ports sur l'hôte. Il monte le socket Docker en lecture seule — c'est ainsi qu'il découvre les serveurs MCP — et ses fichiers de configuration.
traefik: image: traefik:v3.6.17@sha256:802adc80a7bb20a6766c9385c2ad547f0de98564cd20d31d0b6d8f726f906f66 restart: unless-stopped ports: - "8090:80" # redirection HTTP -> HTTPS - "443:443" # endpoint MCP en TLS - "8088:8080" # tableau de bord — lab uniquement volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik.yml:/etc/traefik/traefik.yml:ro - ./dynamic.yml:/etc/traefik/dynamic.yml:ro - ./certs:/certs:roChaque serveur MCP est un service qui n'expose aucun port sur l'hôte. Il se contente de déclarer, via des labels Docker, comment Traefik doit le router. Traefik lit ces labels et configure ses routeurs automatiquement.
notes: build: . image: lab-mcp-server restart: unless-stopped environment: MCP_NAME: notes labels: - traefik.enable=true - traefik.http.routers.notes.rule=Host(`notes.mcp.localhost`) - traefik.http.routers.notes.entrypoints=websecure - traefik.http.routers.notes.tls=true - traefik.http.services.notes.loadbalancer.server.port=8000La configuration statique de Traefik — ses points d'entrée et ses fournisseurs — vit dans traefik.yml. C'est là que la redirection systématique de HTTP vers HTTPS est posée, une fois pour toutes.
entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443"providers: docker: exposedByDefault: false file: filename: /etc/traefik/dynamic.ymlRoutage par hôte, TLS et redirection
Section intitulée « Routage par hôte, TLS et redirection »Le routage par nom d'hôte est le cœur de la passerelle. La règle Host(\notes.mcp.localhost`)dit à Traefik : « toute requête dont l'en-têteHostvautnotes.mcp.localhostpart vers le serveur notes ». Trois serveurs, trois hôtes, trois routeurs — et un hôte inconnu reçoit un404` franc, sans jamais atteindre un serveur MCP.
Le TLS est terminé par Traefik. En lab, le certificat est auto-signé pour *.mcp.localhost, généré en une commande :
openssl req -x509 -newkey rsa:2048 -nodes \ -keyout certs/mcp.localhost.key -out certs/mcp.localhost.crt \ -days 825 -subj "/CN=mcp.localhost" \ -addext "subjectAltName=DNS:mcp.localhost,DNS:*.mcp.localhost"Le certificat est déclaré dans la configuration dynamique (dynamic.yml), que Traefik recharge sans redémarrage. En production, on remplace ce certificat auto-signé par un certificat Let's Encrypt : Traefik sait l'obtenir et le renouveler seul, comme l'explique le guide Traefik et TLS avec ACME.
Health checks et limitation de débit
Section intitulée « Health checks et limitation de débit »Un service en production doit dire s'il va bien. Chaque serveur MCP expose une route /healthz ; le compose.yml la transforme en health check Docker.
healthcheck: test: - CMD - python - -c - import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz') interval: 10s timeout: 3s retries: 5 start_period: 10sDocker sonde /healthz toutes les dix secondes. Un conteneur qui échoue passe en unhealthy — visible d'un docker compose ps, et exploitable par un orchestrateur pour le redémarrer ou le retirer de la rotation. C'est la différence entre un serveur « qui tourne » et un serveur dont on sait qu'il répond.
La limitation de débit protège les serveurs d'un afflux de requêtes — un agent en boucle, un client mal réglé. Traefik la fournit comme middleware, déclaré lui aussi en labels :
labels: - traefik.http.middlewares.mcp-ratelimit.ratelimit.average=30 - traefik.http.middlewares.mcp-ratelimit.ratelimit.burst=15 - traefik.http.routers.notes.middlewares=mcp-ratelimitAu-delà de 30 requêtes par seconde en moyenne, Traefik répond 429 Too Many Requests — sans que le serveur MCP soit même sollicité. La passerelle absorbe la pression.
Valider la stack de bout en bout
Section intitulée « Valider la stack de bout en bout »Une stack de déploiement se vérifie comme du code. La suite test_deploiement.py du lab contrôle, sur la stack réellement démarrée, les comportements qui définissent une passerelle correcte.
test_deploiement.py::test_healthz_et_routage_par_hote[notes] PASSEDtest_deploiement.py::test_healthz_et_routage_par_hote[meteo] PASSEDtest_deploiement.py::test_healthz_et_routage_par_hote[recherche] PASSEDtest_deploiement.py::test_hote_inconnu_renvoie_404 PASSEDtest_deploiement.py::test_http_est_redirige_vers_https PASSEDtest_deploiement.py::test_appel_mcp_via_traefik PASSEDChaque test fige une garantie. Les trois premiers vérifient que chaque hôte répond 200 et renvoie son propre nom — la preuve que Traefik a routé vers le bon serveur, pas un autre. L'hôte inconnu reçoit 404. Une requête HTTP est redirigée en 301 vers HTTPS. Et le dernier test traverse toute la chaîne : un appel MCP complet — initialize, list_tools, call_tool — passe par Traefik en TLS jusqu'au serveur et revient. La méthode reprend celle du guide Tester et déboguer un serveur MCP.
Exploiter : logs, sauvegardes, versions
Section intitulée « Exploiter : logs, sauvegardes, versions »Déployer n'est qu'un début ; un service vit. Trois réflexes d'exploitation complètent la stack.
Les logs sont centralisés par Traefik, qui journalise chaque requête — hôte, code de retour, latence — tandis que chaque serveur MCP écrit ses propres logs applicatifs sur la sortie d'erreur, récupérés par docker compose logs. Un journal d'accès uniforme en façade plus des journaux applicatifs par serveur : c'est le minimum pour diagnostiquer un incident.
Les sauvegardes ne concernent que l'état. Un serveur MCP sans état — comme ceux de ce lab — se redéploie à partir de son image, rien à sauvegarder. Dès qu'un serveur écrit des données, en revanche, son volume Docker doit entrer dans votre plan de sauvegarde, au même titre que n'importe quelle base.
Le versioning, enfin, est déjà en place : images épinglées par digest, dépendances par version exacte. Mettre à jour un serveur devient un geste maîtrisé — on change le digest, on rebuild, on redéploie — et réversible : l'ancien digest reste un point de retour connu.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Traefik : client version too old | Daemon Docker récent, Traefik trop ancien | Utiliser une version Traefik récente (3.6+) |
bind: address already in use | Le port 80 ou 443 est déjà pris | Changer le mapping de ports dans compose.yml |
404 sur un hôte attendu | Routeur non découvert | Vérifier traefik.enable=true et la règle Host(...) dans les labels |
Conteneur unhealthy | /healthz ne répond pas | docker compose logs <service> — le serveur a-t-il démarré ? |
| Certificat refusé par le client | Certificat auto-signé | En lab, désactiver la vérification ; en production, passer à Let's Encrypt |
429 Too Many Requests | Limite de débit atteinte | Comportement attendu ; ajuster ratelimit.average si nécessaire |
À retenir
Section intitulée « À retenir »- Un serveur MCP qui touche à des données internes se self-héberge — le SaaS lui donnerait son périmètre d'action à un tiers.
- Une passerelle unique (Traefik) concentre TLS, routage et limitation de débit ; les serveurs MCP restent en HTTP interne.
- L'image se construit reproductible : base épinglée par digest, dépendances par version exacte.
- Le routage par nom d'hôte isole les serveurs ; un hôte inconnu reçoit
404sans atteindre aucun serveur. - Traefik termine le TLS : aucun serveur MCP ne manipule de certificat.
- Le health check transforme « le serveur tourne » en « le serveur répond » — une information exploitable.
- Le versioning par digest rend chaque mise à jour maîtrisée et réversible.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Traefik avec Docker — le fournisseur Docker et le routage par labels en détail.
- Documentation Traefik — middlewares, métriques et observabilité de la passerelle.
- Spécification des transports MCP — la référence du transport Streamable HTTP.