Traefik et HTTPS
On a besoin d’un proxy inverse. Il se place devant tous les conteneurs, effectue la terminaison TLS et achemine les requêtes entrantes vers le bon service en fonction du nom d’hôte. Traefik est le choix naturel pour les environnements Docker : il lit les labels des conteneurs et se configure automatiquement.
On ajoute un label à un conteneur pour indiquer “je veux une route sur ce domaine” et Traefik la crée. Élégant, mais un problème de compatibilité m’a rattrapé dès le départ et m’a coûté un bon moment.
Le problème de version
Les versions récentes de Docker Engine ont modifié la façon dont l’API négocie les versions avec les clients. Traefik v2, la version que l’on trouve dans la plupart des tutoriels, ne gère pas cela correctement. Il se connecte au daemon Docker, semble démarrer sans problème, mais échoue silencieusement à détecter les services.
Les conteneurs tournent. Traefik tourne. Rien n’est routé. Aucun message d’erreur ne pointe clairement vers la cause.
La solution : passer à Traefik v3, la version majeure actuelle, qui gère correctement la négociation de version.
Si on cherche “Traefik Supabase Docker”, la majorité des résultats montre encore une configuration Traefik v2. À garder à l’esprit avant de copier quoi que ce soit.
Configuration DNS
Avant de configurer Traefik, il faut créer des enregistrements DNS pointant vers le serveur. Chez votre fournisseur DNS, créez des enregistrements A pour chaque sous-domaine prévu :
kong.project1.yourdomain.com A YOUR_VPS_IP
studio.project1.yourdomain.com A YOUR_VPS_IP
storage.project1.yourdomain.com A YOUR_VPS_IP
kong.project2.yourdomain.com A YOUR_VPS_IP
studio.project2.yourdomain.com A YOUR_VPS_IP
storage.project2.yourdomain.com A YOUR_VPS_IP
Let’s Encrypt vérifie ces enregistrements lors de l’émission des certificats. La propagation DNS prend généralement quelques minutes, mais peut aller jusqu’à quelques heures selon le fournisseur. Avant de déployer, vérifiez que la propagation est complète. Si dig est disponible (apt install dnsutils -y), lancez dig kong.project1.yourdomain.com +short pour confirmer que l’IP du VPS est bien retournée. Sinon, il suffit d’attendre et de réessayer.
Le stack Traefik
Créez traefik/docker-compose.yml :
networks:
traefik_default:
driver: overlay
attachable: true
services:
traefik:
image: traefik:v3
command:
- "--providers.swarm.network=traefik_default"
- "--providers.swarm.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.email=your@email.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.tlschallenge=true"
- "--log.level=INFO"
- "--api.dashboard=false"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
networks:
- traefik_default
deploy:
placement:
constraints:
- node.role == manager
volumes:
letsencrypt:
Quelques explications s’imposent.
exposedByDefault=false signifie que Traefik ignore tout conteneur qui ne s’inscrit pas explicitement avec traefik.enable=true. Sans ça, chaque conteneur sur le réseau Traefik serait accessible publiquement, autant dire une porte ouverte.
api.dashboard=false désactive l’interface web de Traefik. Le tableau de bord expose l’intégralité de la configuration de routage : tous les noms de services, tous les domaines, tous les middlewares. Aucune raison d’exposer ça.
Le volume letsencrypt stocke les certificats. Il ne faut pas le supprimer entre les déploiements. Let’s Encrypt applique une limite de demandes de certificats par domaine et par semaine. Si on vide le volume et redéploie à répétition, on finit par atteindre cette limite et on ne peut plus obtenir de nouveau certificat pendant plusieurs jours.
Le trafic HTTP (port 80) est redirigé automatiquement vers HTTPS via les règles d’entrypoint.
Un problème de placement de labels
Dans un Docker Compose classique, les labels de service se placent au niveau du service :
services:
myapp:
labels:
traefik.enable: 'true'
Avec docker stack deploy en Docker Swarm, ils se placent dans le bloc deploy :
services:
myapp:
deploy:
labels:
traefik.enable: 'true'
Les labels placés au mauvais niveau sont ignorés silencieusement. Traefik ne routera pas le service et ne donnera aucune indication sur la raison. J’ai découvert ça à force de scruter une configuration d’apparence correcte, avant de finir par trouver l’erreur.
En-têtes de sécurité
Traefik propose un système de middlewares : des composants réutilisables qui traitent les requêtes avant qu’elles n’atteignent un service. On définit un middleware security-headers qui ajoute HSTS, supprime l’en-tête Server, définit une politique de type de contenu, etc.
Dans Swarm, un middleware défini dans un stack est accessible aux autres stacks via le suffixe @swarm. On définit le middleware des en-têtes dans les labels du service Studio de project1 (présentés dans le billet suivant), et project2 y fait référence sous le nom security-headers@swarm. Traefik le détecte automatiquement dès que project1 est déployé.
Le middleware des en-têtes ressemble à ceci dans les labels du service :
traefik.http.middlewares.security-headers.headers.stsSeconds: '63072000'
traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains: 'true'
traefik.http.middlewares.security-headers.headers.stsPreload: 'true'
traefik.http.middlewares.security-headers.headers.forceSTSHeader: 'true'
traefik.http.middlewares.security-headers.headers.contentTypeNosniff: 'true'
traefik.http.middlewares.security-headers.headers.referrerPolicy: strict-origin-when-cross-origin
traefik.http.middlewares.security-headers.headers.customFrameOptionsValue: SAMEORIGIN
traefik.http.middlewares.security-headers.headers.customResponseHeaders.Server: ''
Cette dernière ligne fixe l’en-tête de réponse Server à une chaîne vide. Par défaut, Kong annonce sa version dans chaque réponse. Aucune raison de lui laisser ce plaisir.
Une remarque sur la dépendance inter-stacks : comme le middleware security-headers est défini dans le stack de project1, il disparaît si on arrête complètement project1. Project2 se retrouverait alors sans middleware d’en-têtes. Pour un environnement d’apprentissage, c’est acceptable ; la solution plus propre consiste à définir les middlewares partagés directement dans le stack Traefik.
Comment Kong obtient sa route
Trois services de notre stack Supabase ont besoin d’une route publique : Kong (la passerelle API), Studio (le tableau de bord) et Storage (pour les uploads et downloads de fichiers, en contournant Kong). Tout le reste est interne.
Les labels du service Kong dans notre fichier Compose ressemblent à ceci :
services:
kong:
networks:
- internal
- traefik_default
deploy:
labels:
traefik.enable: 'true'
traefik.http.routers.p1-kong.entrypoints: websecure
traefik.http.routers.p1-kong.rule: Host(`kong.project1.yourdomain.com`)
traefik.http.routers.p1-kong.tls.certresolver: le
traefik.http.routers.p1-kong.middlewares: security-headers@swarm
traefik.http.services.p1-kong.loadbalancer.server.port: '8000'
traefik.swarm.network: traefik_default
Quelques subtilités à noter.
Le nom du routeur est p1-kong. Chaque nom de routeur doit être unique dans l’ensemble des stacks qui tournent dans Swarm. Si deux services enregistrent un routeur nommé kong, Traefik en choisit un et ignore l’autre sans avertissement. Préfixez avec l’identifiant du projet, ce qui devient important dans le billet 6 lorsqu’on ajoute la deuxième instance.
Le label traefik.swarm.network indique à Traefik quel réseau utiliser pour acheminer les requêtes. Kong est connecté à deux réseaux : le réseau Supabase interne et le réseau Traefik. Sans ce label, Traefik pourrait essayer de passer par le réseau interne, qu’il ne peut pas atteindre.
Le loadbalancer.server.port indique à Traefik vers quel port transférer les requêtes à l’intérieur du conteneur. Comme on ne publie pas le port de Kong vers l’hôte, Traefik a besoin de connaître directement le port du conteneur.
Déployer Traefik
docker stack deploy -c traefik/docker-compose.yml traefik
Vérifiez que le service a démarré :
docker service ls
# NAME MODE REPLICAS
# traefik_traefik replicated 1/1
Vérifier SSL plus tard
Après avoir déployé un projet Supabase dans le billet suivant, vérifiez que le certificat a bien été émis :
curl -I https://kong.project1.yourdomain.com/health
# HTTP/2 200
# strict-transport-security: max-age=63072000; includeSubDomains; preload
Et que l’en-tête Server a disparu de la réponse.
Alternatives à Traefik
Cette série utilise Traefik parce qu’il s’intègre naturellement à Docker Swarm via les labels de conteneurs. Supabase documente désormais officiellement Caddy et nginx comme alternatives plus simples, à consulter si on préfère une configuration moins orientée labels.
Conformément au guide proxy officiel de Supabase, on route le trafic Storage directement vers le conteneur storage via Traefik, en contournant Kong. Kong ajoute une surcharge inutile pour les uploads et downloads de fichiers volumineux. On retrouvera les labels Traefik de Storage dans le fichier compose du prochain billet.