← Retour aux tutoriels

Reverse proxy de plusieurs services Docker avec Traefik et HTTPS automatique

traefikdockerreverse proxyhttpslet's encryptroutagedocker composesécurité

Reverse proxy de plusieurs services Docker avec Traefik et HTTPS automatique

Ce tutoriel explique comment exposer plusieurs services Docker derrière un reverse proxy Traefik, avec routage par nom de domaine et certificats HTTPS automatiques via Let’s Encrypt. L’objectif est d’obtenir une architecture propre, maintenable et sécurisée, où chaque application est accessible via https://app1.exemple.com, https://app2.exemple.com, etc., sans devoir gérer manuellement Nginx/Apache ni renouveler des certificats.


Sommaire

  1. Prérequis
  2. Concepts essentiels (Traefik, providers, routers, services, middlewares)
  3. Préparer le serveur (DNS, ports, pare-feu)
  4. Structure de répertoires recommandée
  5. Créer un réseau Docker dédié au reverse proxy
  6. Déployer Traefik avec Docker Compose
  7. Activer HTTPS automatique avec Let’s Encrypt (ACME)
  8. Exposer plusieurs services (exemples concrets)
  9. Redirection HTTP → HTTPS et bonnes pratiques de sécurité
  10. Dashboard Traefik : accès sécurisé
  11. Dépannage (logs, erreurs ACME, routage)
  12. Maintenance et mises à jour

Prérequis

Installation Docker (Debian/Ubuntu)

Si Docker n’est pas installé :

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"

Déconnectez/reconnectez-vous, puis vérifiez :

docker version
docker compose version

Concepts essentiels (Traefik, providers, routers, services, middlewares)

Traefik est un reverse proxy dynamique : il découvre automatiquement les services (par exemple via Docker) et configure le routage sans recharger manuellement des fichiers de configuration.

Les notions clés

Pourquoi Traefik avec Docker ?


Préparer le serveur (DNS, ports, pare-feu)

DNS

Créez des enregistrements DNS de type A (ou AAAA en IPv6) :

Vous pouvez aussi utiliser un wildcard *.exemple.com si votre DNS le permet. Pour ce tutoriel, un A record par sous-domaine suffit.

Vérification :

dig +short whoami.exemple.com

Pare-feu

Assurez-vous que les ports 80 et 443 sont ouverts. Avec UFW :

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

Structure de répertoires recommandée

On va organiser proprement les fichiers :

sudo mkdir -p /opt/traefik
sudo mkdir -p /opt/traefik/data
sudo mkdir -p /opt/stacks
sudo chown -R $USER:$USER /opt/traefik /opt/stacks

Créer un réseau Docker dédié au reverse proxy

L’idée : Traefik et les services exposés partagent un réseau Docker commun (bridge). Cela évite d’exposer des ports au host pour chaque service.

Création du réseau :

docker network create proxy
docker network ls | grep proxy

Ce réseau s’appellera proxy et sera référencé dans les docker-compose.yml.


Déployer Traefik avec Docker Compose

Créez /opt/traefik/docker-compose.yml :

cd /opt/traefik
nano docker-compose.yml

Collez ce contenu (et adaptez exemple.com + email) :

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped

    command:
      # Logs
      - --log.level=INFO

      # EntryPoints
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # Provider Docker
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false

      # API/Dashboard (on l'exposera via router + auth)
      - --api.dashboard=true

      # ACME / Let's Encrypt (HTTP-01)
      - --certificatesresolvers.le.acme.email=admin@exemple.com
      - --certificatesresolvers.le.acme.storage=/data/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      # (Optionnel) Redirection globale HTTP->HTTPS via entrypoint
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data:/data

    networks:
      - proxy

networks:
  proxy:
    external: true

Explications importantes

Droits sur acme.json

Traefik exige souvent des permissions strictes sur le fichier de stockage ACME.

touch /opt/traefik/data/acme.json
chmod 600 /opt/traefik/data/acme.json
ls -l /opt/traefik/data/acme.json

Démarrage

cd /opt/traefik
docker compose up -d
docker ps | grep traefik
docker logs traefik --tail 50

Activer HTTPS automatique avec Let’s Encrypt (ACME)

Comment fonctionne le challenge HTTP-01

Let’s Encrypt doit vérifier que vous contrôlez le domaine. En HTTP-01 :

  1. Vous demandez un certificat pour whoami.exemple.com.
  2. Let’s Encrypt appelle http://whoami.exemple.com/.well-known/acme-challenge/...
  3. Traefik répond automatiquement au challenge sur l’entrypoint web (port 80).
  4. Si OK, Let’s Encrypt délivre le certificat, Traefik le stocke dans acme.json et sert ensuite en TLS sur 443.

Points de blocage fréquents :

Vérifier que 80/443 sont libres

sudo ss -lntp | grep -E ':80|:443' || true

Si vous voyez Nginx/Apache, il faut les arrêter ou changer de stratégie.


Exposer plusieurs services (exemples concrets)

Le principe : chaque service Docker à exposer reçoit des labels Traefik. Traefik lit ces labels et crée automatiquement le router + TLS.

Exemple 1 : whoami (service de test)

whoami est un petit service HTTP qui renvoie des infos sur la requête. Idéal pour tester rapidement.

Créez /opt/stacks/whoami/docker-compose.yml :

mkdir -p /opt/stacks/whoami
cd /opt/stacks/whoami
nano docker-compose.yml
services:
  whoami:
    image: traefik/whoami:latest
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true

      # Router HTTPS
      - traefik.http.routers.whoami.rule=Host(`whoami.exemple.com`)
      - traefik.http.routers.whoami.entrypoints=websecure
      - traefik.http.routers.whoami.tls=true
      - traefik.http.routers.whoami.tls.certresolver=le

      # Service (port interne exposé par whoami)
      - traefik.http.services.whoami.loadbalancer.server.port=80

networks:
  proxy:
    external: true

Démarrez :

docker compose up -d
docker logs whoami --tail 50

Test :

curl -I https://whoami.exemple.com
curl https://whoami.exemple.com

Si tout est correct, la première requête peut prendre quelques secondes (temps d’obtention du certificat). Ensuite, vous devez voir un certificat valide.


Exemple 2 : Portainer

Portainer est une interface web pour gérer Docker. On va l’exposer en HTTPS via Traefik, sans publier son port au host.

Créez /opt/stacks/portainer/docker-compose.yml :

mkdir -p /opt/stacks/portainer
cd /opt/stacks/portainer
nano docker-compose.yml
services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    networks:
      - proxy
    labels:
      - traefik.enable=true

      - traefik.http.routers.portainer.rule=Host(`portainer.exemple.com`)
      - traefik.http.routers.portainer.entrypoints=websecure
      - traefik.http.routers.portainer.tls=true
      - traefik.http.routers.portainer.tls.certresolver=le

      - traefik.http.services.portainer.loadbalancer.server.port=9000

volumes:
  portainer_data:

networks:
  proxy:
    external: true

Démarrage :

docker compose up -d
docker logs portainer --tail 50

Accès : https://portainer.exemple.com

Remarque sécurité : Portainer a accès au socket Docker, donc c’est un composant sensible. Protégez l’accès (mot de passe fort, éventuellement IP allowlist via middleware, ou VPN).


Exemple 3 : une application web derrière un chemin ou un sous-domaine

Le plus simple et robuste : un sous-domaine par application. Le routage par chemin (https://exemple.com/app) fonctionne aussi, mais peut nécessiter des ajustements (base path, headers, réécriture).

Variante A : sous-domaine (recommandé)

Exemple avec une app nginx :

mkdir -p /opt/stacks/app-nginx
cd /opt/stacks/app-nginx
nano docker-compose.yml
services:
  app:
    image: nginx:alpine
    container_name: app-nginx
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true

      - traefik.http.routers.appnginx.rule=Host(`app.exemple.com`)
      - traefik.http.routers.appnginx.entrypoints=websecure
      - traefik.http.routers.appnginx.tls=true
      - traefik.http.routers.appnginx.tls.certresolver=le

      - traefik.http.services.appnginx.loadbalancer.server.port=80

networks:
  proxy:
    external: true

Démarrage :

docker compose up -d
curl -I https://app.exemple.com

Variante B : routage par chemin (à utiliser avec prudence)

Si vous voulez https://exemple.com/app, vous pouvez utiliser une règle PathPrefix et parfois un middleware StripPrefix.

Exemple (attention, certaines apps cassent si elles ne supportent pas d’être servies sous un sous-chemin) :

labels:
  - traefik.enable=true
  - traefik.http.routers.monapp.rule=Host(`exemple.com`) && PathPrefix(`/app`)
  - traefik.http.routers.monapp.entrypoints=websecure
  - traefik.http.routers.monapp.tls=true
  - traefik.http.routers.monapp.tls.certresolver=le
  - traefik.http.middlewares.monapp-stripprefix.stripprefix.prefixes=/app
  - traefik.http.routers.monapp.middlewares=monapp-stripprefix
  - traefik.http.services.monapp.loadbalancer.server.port=8080

Redirection HTTP → HTTPS et bonnes pratiques de sécurité

Nous avons déjà configuré une redirection globale via :

Cela force toutes les requêtes en HTTP vers HTTPS, ce qui est généralement souhaitable.

Ajouter des headers de sécurité (middleware)

Vous pouvez définir un middleware de headers (HSTS, X-Frame-Options, etc.). Avec Traefik, on peut le faire via labels sur Traefik lui-même (ou via un fichier dynamique). Pour rester simple, voici un middleware réutilisable défini sur le conteneur Traefik via labels, puis appliqué aux routers. Cela évite de répéter les headers partout.

Modifiez /opt/traefik/docker-compose.yml et ajoutez des labels au service traefik :

    labels:
      - traefik.enable=true

      # Middleware headers sécurisé
      - traefik.http.middlewares.sec-headers.headers.framedeny=true
      - traefik.http.middlewares.sec-headers.headers.contenttypenosniff=true
      - traefik.http.middlewares.sec-headers.headers.referrerpolicy=no-referrer
      - traefik.http.middlewares.sec-headers.headers.permissionspolicy=geolocation=(), microphone=(), camera=()
      - traefik.http.middlewares.sec-headers.headers.stsincludesubdomains=true
      - traefik.http.middlewares.sec-headers.headers.stspreload=true
      - traefik.http.middlewares.sec-headers.headers.stsseconds=31536000

Puis, sur chaque service exposé, ajoutez :

- traefik.http.routers.whoami.middlewares=sec-headers@docker

Le suffixe @docker indique que le middleware vient du provider Docker.

Appliquez les changements :

cd /opt/traefik
docker compose up -d

Dashboard Traefik : accès sécurisé

Le dashboard est très utile, mais ne doit pas être exposé sans protection.

Étape 1 : créer un mot de passe hashé (Basic Auth)

Installez apache2-utils (pour htpasswd) :

sudo apt-get update
sudo apt-get install -y apache2-utils

Générez un hash (remplacez admin par votre login) :

htpasswd -nb admin 'MotDePasseTresFort'

Sortie typique :

admin:$apr1$...

Copiez la ligne complète.

Étape 2 : exposer le dashboard via un router + middleware auth

Modifiez /opt/traefik/docker-compose.yml et ajoutez des labels au service traefik (en plus des headers si vous les avez mis) :

    labels:
      - traefik.enable=true

      # Router vers l'API/dashboard interne
      - traefik.http.routers.traefik.rule=Host(`traefik.exemple.com`)
      - traefik.http.routers.traefik.entrypoints=websecure
      - traefik.http.routers.traefik.tls=true
      - traefik.http.routers.traefik.tls.certresolver=le
      - traefik.http.routers.traefik.service=api@internal

      # Auth basique
      - traefik.http.middlewares.traefik-auth.basicauth.users=admin:$apr1$XXXXXXXXXXXXXXX

      # Appliquer auth + headers
      - traefik.http.routers.traefik.middlewares=traefik-auth@docker,sec-headers@docker

Redémarrez :

cd /opt/traefik
docker compose up -d
docker logs traefik --tail 100

Accédez ensuite à : https://traefik.exemple.com


Dépannage (logs, erreurs ACME, routage)

Voir les logs Traefik

docker logs traefik -f

Augmentez temporairement le niveau de logs :

Dans command, remplacez --log.level=INFO par :

Puis :

docker compose up -d

Erreurs fréquentes Let’s Encrypt

1) Timeout during connect ou connection refused

2) Too many certificates already issued

Let’s Encrypt limite les émissions. Solutions :

Traefik permet un serveur ACME staging via : --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory

Important : les certifs staging ne sont pas reconnus comme valides par les navigateurs, mais parfaits pour tester.

3) Certificat non délivré, mais routage OK

Regardez acme.json :

sudo cat /opt/traefik/data/acme.json | head -c 200

S’il reste vide, Traefik n’a pas réussi à finaliser ACME.

Vérifier les routers/services détectés

Le dashboard Traefik est la meilleure vue : vous y verrez les routers, règles, entrypoints, middlewares, erreurs.

Vérifier la connectivité réseau Docker

Assurez-vous que Traefik et vos services sont sur le même réseau proxy :

docker network inspect proxy | sed -n '1,120p'

Vous devez voir traefik et les conteneurs applicatifs dans Containers.


Maintenance et mises à jour

Mettre à jour Traefik

cd /opt/traefik
docker compose pull
docker compose up -d
docker image prune -f

Mettre à jour une stack applicative

Exemple pour whoami :

cd /opt/stacks/whoami
docker compose pull
docker compose up -d

Sauvegardes à ne pas oublier

Sauvegarde rapide des volumes (exemple Portainer) :

docker run --rm \
  -v portainer_portainer_data:/data \
  -v /opt/backups:/backup \
  alpine sh -c "tar czf /backup/portainer_data_$(date +%F).tar.gz -C /data ."

Adaptez le nom du volume (il dépend du nom du projet Compose).


Conclusion

Vous avez maintenant :

Pour ajouter un nouveau service, la logique reste identique :

  1. Le connecter au réseau proxy
  2. Mettre traefik.enable=true
  3. Définir un router Host(...) + websecure + tls.certresolver=le
  4. Définir le port interne via loadbalancer.server.port

Si vous voulez aller plus loin, les prochaines améliorations typiques sont :