Retour au cours

infra / docker-compose

Reverse proxy dynamique avec Traefik

Leçon 161 exercice

Explication

Ce que vous allez apprendre

  • Comprendre la différence entre un reverse proxy à configuration statique (nginx) et dynamique (Traefik)
  • Exposer un service via des labels Docker plutôt qu'en éditant un fichier de configuration
  • Sécuriser l'exposition par défaut avec exposedbydefault=false
  • Obtenir et renouveler automatiquement un certificat HTTPS via Let's Encrypt
  • Ajouter de l'authentification et du rate limiting avec des middlewares Traefik

Dans quel contexte ?

L'infrastructure de boutique-api ajoute régulièrement de nouveaux sous-domaines (admin.example.com, api.example.com) et l'équipe DevOps en a assez d'éditer et recharger nginx.conf à chaque déploiement. Avec Traefik, un nouveau service qui porte les bons labels Docker est automatiquement routé, avec son certificat HTTPS généré tout seul, sans jamais toucher un fichier de configuration central.

Le problème que résout un reverse proxy

D'abord, rappelle-toi la leçon sur le scaling : plusieurs services derrière un seul point d'entrée public. nginx sait déjà faire ça, mais il faut éditer son fichier nginx.conf à la main à chaque ajout de service, puis recharger la configuration.

Une solution figée face à un besoin qui change souvent

Sur une stack qui évolue vite, avec des services qui apparaissent et disparaissent, cette édition manuelle devient vite un frein. Il faudrait un proxy qui se mette à jour tout seul, sans intervention humaine à chaque déploiement.

L'idée de Traefik : observer plutôt qu'être configuré

C'est exactement ce que fait Traefik. Il se connecte au socket Docker, /var/run/docker.sock, en LECTURE SEULE, et observe en direct la liste des conteneurs qui démarrent ou s'arrêtent.

Comment un service se fait connaître de Traefik

Un service qui veut être routé n'édite aucun fichier : il porte simplement des labels Docker, comme traefik.enable=true et traefik.http.routers.api.rule=Host(\api.example.com`)`. Traefik lit ces labels en continu et met à jour son routage dès qu'un label change.

Un choix de sécurité qui inverse la logique par défaut

Une fois ce mécanisme compris, il reste une question : que se passe-t-il pour un service qui ne porte aucun label ? Avec l'option --providers.docker.exposedbydefault=false, la réponse est simple : rien n'est exposé par défaut, il faut le déclarer explicitement avec traefik.enable=true, exactement le principe du moindre privilège vu en leçon sur la production.

Piège fréquent

Monter le socket Docker en lecture-écriture (/var/run/docker.sock:/var/run/docker.sock sans :ro) donne à Traefik le pouvoir de créer ou détruire n'importe quel conteneur sur la machine, bien au-delà de ce dont il a besoin pour router du trafic. Monte-le TOUJOURS en lecture seule avec :ro.

Le certificat HTTPS géré tout seul

Pour aller plus loin, Traefik peut aussi obtenir et renouveler automatiquement un certificat TLS via Let's Encrypt, grâce à certificatesresolvers.le.acme.httpchallenge=true. Il suffit d'ajouter tls.certresolver=le sur le routeur concerné pour que le renouvellement se fasse sans jamais toucher un certificat à la main.

Ajouter des règles supplémentaires avec les middlewares

Une fois le routage et le TLS en place, les middlewares ajoutent des comportements supplémentaires, comme admin-auth pour une authentification basique ou admin-ratelimit pour limiter le nombre de requêtes par seconde. Ils se déclarent eux aussi en labels, et se combinent sur un même routeur.

Les pièges à connaître

Le piège le plus grave est d'exposer le dashboard Traefik sans authentification : n'importe qui pourrait alors voir la topologie complète de ton infrastructure. Deuxième piège : monter le socket Docker en lecture-écriture au lieu de lecture seule, ce qui donnerait à Traefik le pouvoir de créer ou détruire des conteneurs. Enfin, oublier le certresolver sur un routeur empêche simplement le HTTPS de fonctionner sur ce service.

nginx (config statique)Traefik (config dynamique)
Fichier nginx.conf édité manuellementLabels Docker lus automatiquement
Rechargement nécessaire à chaque changementMise à jour instantanée à chaque déploiement
TLS géré manuellement (certbot, cron)Renouvellement automatique via ACME/Let's Encrypt

Maintenant que le trafic est routé automatiquement, la leçon suivante s'attaque à un problème différent mais tout aussi sensible en production : exécuter une migration de base de données une seule fois, même si l'application tourne sur plusieurs instances.

Commandes & code

Reverse proxy dynamique avec Traefik

Contrairement à nginx (config statique), Traefik découvre les services automatiquement via des labels Docker, sans reload manuel.

yaml
services:
  traefik:
    image: traefik:v3.1
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"   # rien n'est exposé sans label explicite
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=ops@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro   # lecture seule : Traefik observe, ne modifie rien
      - traefik_certs:/letsencrypt
    networks:
      - proxy_net

  api:
    build: ./api
    expose:
      - "3000"                 # pas de "ports:" : seul Traefik doit être joignable depuis l'extérieur
    networks:
      - proxy_net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`api.example.com`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls.certresolver=le"
      - "traefik.http.services.api.loadbalancer.server.port=3000"

networks:
  proxy_net:

volumes:
  traefik_certs:
yaml
# Middlewares : auth basique + rate limiting + redirection HTTP -> HTTPS
services:
  admin:
    build: ./admin
    expose:
      - "8080"
    networks:
      - proxy_net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.admin.rule=Host(`admin.example.com`)"
      - "traefik.http.routers.admin.entrypoints=websecure"
      - "traefik.http.routers.admin.tls.certresolver=le"
      - "traefik.http.routers.admin.middlewares=admin-auth,admin-ratelimit"
      # htpasswd généré via : htpasswd -nb admin s3cr3t
      - "traefik.http.middlewares.admin-auth.basicauth.users=admin:$$apr1$$xyz..."
      - "traefik.http.middlewares.admin-ratelimit.ratelimit.average=10"
      - "traefik.http.middlewares.admin-ratelimit.ratelimit.burst=20"

  # Redirection globale HTTP -> HTTPS au niveau de l'entrypoint "web"
  traefik:
    command:
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
bash
# Dashboard Traefik : à ne JAMAIS exposer sans authentification en production
# --api.dashboard=true + labels avec middleware "admin-auth" comme ci-dessus

docker compose logs -f traefik
docker compose exec traefik traefik healthcheck

Résumé

  • providers.docker fait découvrir les services automatiquement via leurs labels, sans reload.
  • exposedbydefault=false + traefik.enable=true évite d'exposer un service par erreur.
  • Le certresolver ACME gère le renouvellement TLS automatiquement, sans certificat manuel.
  • Les middlewares (auth, rate limit, redirection) se déclarent aussi en labels, composables par routeur.

Exercices pratiques

1 disponible
1

Mission : un nouveau sous-domaine admin exposé sans protection

Objectif : Sécuriser un service routé par Traefik et évaluer le risque d'un socket Docker monté en écriture.

Contexte

L'équipe de boutique-api déploie un nouveau service admin avec le label traefik.enable=true, mais sans label traefik.http.routers.admin.tls.certresolver=le. Le socket Docker est monté sur Traefik via /var/run/docker.sock:/var/run/docker.sock, SANS :ro.

Corrige ces deux problèmes de configuration Traefik.

Résoudre l’exercice →