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
- Prérequis
- Concepts essentiels (Traefik, providers, routers, services, middlewares)
- Préparer le serveur (DNS, ports, pare-feu)
- Structure de répertoires recommandée
- Créer un réseau Docker dédié au reverse proxy
- Déployer Traefik avec Docker Compose
- Activer HTTPS automatique avec Let’s Encrypt (ACME)
- Exposer plusieurs services (exemples concrets)
- Redirection HTTP → HTTPS et bonnes pratiques de sécurité
- Dashboard Traefik : accès sécurisé
- Dépannage (logs, erreurs ACME, routage)
- Maintenance et mises à jour
Prérequis
- Un serveur Linux (Debian/Ubuntu conseillés) avec accès root ou sudo.
- Docker et Docker Compose (plugin
docker compose). - Un nom de domaine (ex.
exemple.com) et la possibilité de créer des enregistrements DNS. - Ports 80 et 443 accessibles depuis Internet (indispensable pour Let’s Encrypt en challenge HTTP-01).
- Connaissances de base Docker (conteneurs, réseaux, volumes).
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
- EntryPoints : points d’entrée réseau, typiquement
web(port 80) etwebsecure(port 443). - Providers : sources de configuration dynamique. Ici, le provider Docker lit les labels des conteneurs.
- Routers : règles de routage (ex.
Host(app1.exemple.com)), associées à un entrypoint. - Services : backend(s) vers lesquels Traefik envoie le trafic (souvent un conteneur + port interne).
- Middlewares : transformations/contrôles appliqués aux requêtes (redirection HTTPS, auth basique, headers sécurité…).
- ACME / Let’s Encrypt : mécanisme d’obtention et renouvellement automatique de certificats TLS.
Pourquoi Traefik avec Docker ?
- Découverte automatique des conteneurs.
- Configuration par labels : versionnable, lisible, proche de l’application.
- HTTPS automatique : pas de gestion manuelle des certificats.
- Support de multiples services et règles (sous-domaines, chemins, priorités).
Préparer le serveur (DNS, ports, pare-feu)
DNS
Créez des enregistrements DNS de type A (ou AAAA en IPv6) :
traefik.exemple.com→ IP du serveur (optionnel)whoami.exemple.com→ IP du serveurportainer.exemple.com→ IP du serveur- etc.
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
/opt/traefik: stack Traefik/opt/traefik/data: stockage des certificats ACME (fichieracme.json)/opt/stacks: stacks applicatives (portainer, apps, etc.)
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
--providers.docker.exposedbydefault=false: sécurité. Aucun conteneur n’est exposé tant que vous ne mettez pastraefik.enable=true.docker.socken lecture seule : Traefik doit lire les métadonnées Docker (labels, réseaux, ports). Le socket reste sensible ; on limite au:roet on restreint l’exposition.- Redirection HTTP→HTTPS : ici on la fait globalement au niveau de l’entrypoint
web. C’est simple et efficace. acme.json: Traefik y stocke les certificats et comptes ACME. Il doit être persistant.
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 :
- Vous demandez un certificat pour
whoami.exemple.com. - Let’s Encrypt appelle
http://whoami.exemple.com/.well-known/acme-challenge/... - Traefik répond automatiquement au challenge sur l’entrypoint
web(port 80). - Si OK, Let’s Encrypt délivre le certificat, Traefik le stocke dans
acme.jsonet sert ensuite en TLS sur 443.
Points de blocage fréquents :
- DNS incorrect (le domaine ne pointe pas sur le serveur).
- Port 80 filtré.
- Un autre service écoute déjà sur 80/443.
- Reverse proxy déjà en place.
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 :
--entrypoints.web.http.redirections.entrypoint.to=websecure--entrypoints.web.http.redirections.entrypoint.scheme=https
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 :
--log.level=DEBUG
Puis :
docker compose up -d
Erreurs fréquentes Let’s Encrypt
1) Timeout during connect ou connection refused
- Vérifiez DNS :
dig +short whoami.exemple.com - Vérifiez ports :
sudo ss -lntp | grep -E ':80|:443' - Vérifiez firewall :
sudo ufw status - Vérifiez que l’IP publique du serveur est bien celle ciblée.
2) Too many certificates already issued
Let’s Encrypt limite les émissions. Solutions :
- Attendre la fin de la fenêtre de rate limit.
- Utiliser l’environnement de staging pour tester.
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
/opt/traefik/data/acme.json: contient les certificats (sinon Traefik devra les redemander).- Volumes applicatifs (ex.
portainer_data).
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 :
- Un reverse proxy Traefik en frontal sur 80/443
- Un réseau Docker
proxypartagé - Des services exposés sans publier leurs ports sur le host
- Des certificats Let’s Encrypt délivrés et renouvelés automatiquement
- Un dashboard Traefik sécurisé par auth basique
- Une base solide pour ajouter des middlewares (headers, IP allowlist, rate limit, authentification, etc.)
Pour ajouter un nouveau service, la logique reste identique :
- Le connecter au réseau
proxy - Mettre
traefik.enable=true - Définir un router
Host(...)+websecure+tls.certresolver=le - Définir le port interne via
loadbalancer.server.port
Si vous voulez aller plus loin, les prochaines améliorations typiques sont :
- IP allowlist pour les services admin (Portainer, dashboard)
- Authentification plus robuste (ForwardAuth, OAuth2 Proxy)
- Séparation staging/production ACME
- Observabilité (metrics Prometheus, logs structurés)