Dans mon article précédent, on a déployé une infrastructure Scaleway avec Terraform. Aujourd'hui, on passe à l'étape suivante : déployer une vraie application avec Docker Compose, Traefik comme reverse proxy, et des certificats SSL automatiques via Let's Encrypt.
Ce qu'on va construire
À la fin de cet article, tu auras :
- Traefik v3 configuré comme reverse proxy
- Let's Encrypt pour des certificats SSL automatiques et gratuits
- Un Docker Socket Proxy pour sécuriser l'accès au Docker socket
- Des middlewares de sécurité (headers, redirection HTTPS)
- Une application exemple exposée en HTTPS
- Le dashboard Traefik sécurisé avec authentification
Prérequis
- Un serveur avec Docker installé (voir mon article sur Scaleway)
- Un nom de domaine pointant vers l'IP de ton serveur
- Les ports 80 et 443 ouverts dans ton firewall
Pourquoi Traefik ?
Traefik est un reverse proxy moderne conçu pour le cloud. Ses avantages :
- Découverte automatique : Traefik détecte les containers Docker et les expose automatiquement
- Let's Encrypt intégré : Génération et renouvellement automatique des certificats SSL
- Configuration dynamique : Pas besoin de redémarrer pour ajouter un nouveau service
- Dashboard : Interface web pour visualiser tes routes
Architecture des fichiers
1/opt/docker/
2├── traefik/
3│ ├── docker-compose.yml # Stack Traefik
4│ ├── acme.json # Certificats Let's Encrypt (généré)
5│ └── dynamic/
6│ └── middlewares.yml # Middlewares réutilisables
7└── apps/
8 └── whoami/
9 └── docker-compose.yml # Application exempleOn sépare Traefik des applications. Ça permet de redémarrer une app sans toucher au reverse proxy.
Étape 1 : Créer le réseau Docker
Traefik et les applications doivent partager un réseau Docker pour communiquer :
1docker network create proxyCe réseau proxy sera utilisé par tous les services exposés publiquement.
Étape 2 : Configurer Traefik
docker-compose.yml
Crée le fichier /opt/docker/traefik/docker-compose.yml :
1services:
2 # Proxy sécurisé pour le socket Docker
3 socket-proxy:
4 image: tecnativa/docker-socket-proxy:0.4
5 restart: unless-stopped
6 environment:
7 CONTAINERS: 1
8 NETWORKS: 1
9 SERVICES: 0
10 TASKS: 0
11 POST: 0
12 volumes:
13 - /var/run/docker.sock:/var/run/docker.sock:ro
14 networks:
15 - socket-proxy
16
17 traefik:
18 image: traefik:v3.6
19 container_name: traefik
20 restart: unless-stopped
21 depends_on:
22 - socket-proxy
23 security_opt:
24 - no-new-privileges:true
25 command:
26 # API et Dashboard
27 - "--api.dashboard=true"
28
29 # Provider Docker via socket proxy
30 - "--providers.docker=true"
31 - "--providers.docker.endpoint=tcp://socket-proxy:2375"
32 - "--providers.docker.exposedbydefault=false"
33 - "--providers.docker.network=proxy"
34
35 # Provider fichiers pour middlewares
36 - "--providers.file.directory=/etc/traefik/dynamic"
37 - "--providers.file.watch=true"
38
39 # Entrypoints
40 - "--entrypoints.web.address=:80"
41 - "--entrypoints.websecure.address=:443"
42
43 # Redirection HTTP -> HTTPS globale
44 - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
45 - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
46
47 # Let's Encrypt
48 - "--certificatesresolvers.letsencrypt.acme.email=ton@email.com"
49 - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
50 - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
51
52 # Logs
53 - "--log.level=INFO"
54 ports:
55 - "80:80"
56 - "443:443"
57 volumes:
58 - ./acme.json:/letsencrypt/acme.json
59 - ./dynamic:/etc/traefik/dynamic:ro
60 networks:
61 - proxy
62 - socket-proxy
63 labels:
64 # Dashboard Traefik
65 - "traefik.enable=true"
66 - "traefik.http.routers.traefik.rule=Host(`traefik.ton-domaine.com`)"
67 - "traefik.http.routers.traefik.entrypoints=websecure"
68 - "traefik.http.routers.traefik.tls=true"
69 - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
70 - "traefik.http.routers.traefik.service=api@internal"
71 - "traefik.http.routers.traefik.middlewares=auth@file,security-headers@file"
72
73networks:
74 proxy:
75 external: true
76 socket-proxy:
77 driver: bridgePoints importants
Docker Socket Proxy : Au lieu de monter directement /var/run/docker.sock dans Traefik (risque de sécurité majeur), on utilise un proxy qui limite l'accès à l'API Docker. Seules les opérations de lecture des containers et réseaux sont autorisées.
exposedbydefault=false : Les containers ne sont exposés que s'ils ont explicitement le label traefik.enable=true. Plus sûr.
Redirection HTTPS globale : Configurée au niveau de l'entrypoint web, toutes les requêtes HTTP sont automatiquement redirigées vers HTTPS.
Préparer le fichier acme.json
Let's Encrypt stocke les certificats dans ce fichier. Il doit avoir des permissions restrictives :
1touch /opt/docker/traefik/acme.json
2chmod 600 /opt/docker/traefik/acme.jsonÉtape 3 : Configurer les middlewares
Les middlewares sont des composants réutilisables qui modifient les requêtes. Crée le fichier /opt/docker/traefik/dynamic/middlewares.yml :
1http:
2 middlewares:
3 # Headers de sécurité
4 security-headers:
5 headers:
6 browserXssFilter: true
7 contentTypeNosniff: true
8 frameDeny: true
9 stsIncludeSubdomains: true
10 stsPreload: true
11 stsSeconds: 31536000
12 customFrameOptionsValue: "SAMEORIGIN"
13 referrerPolicy: "strict-origin-when-cross-origin"
14
15 # Authentification basique pour le dashboard
16 auth:
17 basicAuth:
18 users:
19 - "admin:$2y$05$your_hashed_password_here"Générer le mot de passe hashé
Pour l'authentification du dashboard, génère un hash avec htpasswd :
1# Installer htpasswd si nécessaire
2sudo apt-get install apache2-utils
3
4# Générer le hash (remplace 'admin' et 'motdepasse')
5htpasswd -nB adminLa commande te demande un mot de passe et affiche quelque chose comme :
admin:$2y$05$LqKVSNp...
Copie cette ligne dans le fichier middlewares.yml.
Note : Les $ ne doivent PAS être doublés dans les fichiers YAML externes. Le doublement ($$) n'est nécessaire que dans les labels docker-compose pour éviter l'interprétation comme variable d'environnement.
Étape 4 : Démarrer Traefik
1cd /opt/docker/traefik
2docker compose up -dVérifie les logs :
1docker compose logs -f traefikTu devrais voir Traefik démarrer et se connecter au socket proxy.
Étape 5 : Déployer une application
Testons avec l'image whoami, un service simple qui affiche des infos sur la requête.
Crée /opt/docker/apps/whoami/docker-compose.yml :
1services:
2 whoami:
3 image: traefik/whoami
4 container_name: whoami
5 restart: unless-stopped
6 networks:
7 - proxy
8 labels:
9 - "traefik.enable=true"
10
11 # Router HTTPS
12 - "traefik.http.routers.whoami.rule=Host(`whoami.ton-domaine.com`)"
13 - "traefik.http.routers.whoami.entrypoints=websecure"
14 - "traefik.http.routers.whoami.tls=true"
15 - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
16 - "traefik.http.routers.whoami.middlewares=security-headers@file"
17
18networks:
19 proxy:
20 external: trueDémarre l'application :
1cd /opt/docker/apps/whoami
2docker compose up -dCe qui se passe
- Traefik détecte le nouveau container via le socket proxy
- Il lit les labels et crée automatiquement une route
- Il demande un certificat à Let's Encrypt via le HTTP-01 challenge
- Le service est accessible en HTTPS avec un certificat valide
Visite https://whoami.ton-domaine.com pour vérifier.
Exemple concret : déployer une app web
Voici un exemple plus réaliste avec une application Node.js et sa base de données :
1services:
2 app:
3 image: node:20-alpine
4 container_name: myapp
5 restart: unless-stopped
6 working_dir: /app
7 volumes:
8 - ./src:/app
9 command: node server.js
10 environment:
11 - DATABASE_URL=postgresql://user:password@db:5432/myapp
12 networks:
13 - proxy
14 - internal
15 labels:
16 - "traefik.enable=true"
17 - "traefik.http.routers.myapp.rule=Host(`app.ton-domaine.com`)"
18 - "traefik.http.routers.myapp.entrypoints=websecure"
19 - "traefik.http.routers.myapp.tls=true"
20 - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
21 - "traefik.http.routers.myapp.middlewares=security-headers@file"
22 - "traefik.http.services.myapp.loadbalancer.server.port=3000"
23
24 db:
25 image: postgres:16-alpine
26 container_name: myapp-db
27 restart: unless-stopped
28 environment:
29 - POSTGRES_USER=user
30 - POSTGRES_PASSWORD=password
31 - POSTGRES_DB=myapp
32 volumes:
33 - db-data:/var/lib/postgresql/data
34 networks:
35 - internal
36
37networks:
38 proxy:
39 external: true
40 internal:
41 driver: bridge
42
43volumes:
44 db-data:Points clés :
- L'app est sur deux réseaux :
proxy(pour Traefik) etinternal(pour la DB) - La DB n'est que sur
internal, donc pas accessible depuis l'extérieur - Le label
loadbalancer.server.portindique sur quel port l'app écoute
Environnement de staging Let's Encrypt
Let's Encrypt a des limites de rate strictes. Pendant tes tests, utilise le serveur staging :
1command:
2 # ... autres options ...
3 - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"Les certificats staging ne sont pas valides (ton navigateur affichera un avertissement), mais tu ne risques pas d'être bloqué par les rate limits.
Une fois tes tests terminés, supprime la ligne --certificatesresolvers.letsencrypt.acme.caserver=... de ton docker-compose.yml. Sans cette option, Traefik utilisera automatiquement le serveur de production Let's Encrypt et générera des certificats valides.
Monitoring et dépannage
Vérifier les routes actives
Accède au dashboard Traefik via https://traefik.ton-domaine.com (protégé par authentification).
Tu peux aussi inspecter les logs :
1# Logs Traefik
2docker compose logs -f traefik
3
4# Logs du socket proxy
5docker compose logs -f socket-proxyProblèmes courants
Le certificat n'est pas généré :
- Vérifie que le port 80 est accessible depuis Internet
- Vérifie les logs :
docker compose logs traefik - Assure-toi que le DNS pointe vers ton serveur
Le service n'est pas découvert :
- Vérifie que le container est sur le réseau
proxy - Vérifie que le label
traefik.enable=trueest présent - Vérifie les logs du socket-proxy
Erreur 502 Bad Gateway :
- Le container de l'app n'est pas démarré
- Le port spécifié dans
loadbalancer.server.portest incorrect - L'app n'écoute pas sur le bon port
Bonnes pratiques
1. Un réseau par contexte
1networks:
2 proxy: # Pour les services exposés
3 external: true
4 internal: # Pour la communication interne
5 driver: bridge2. Toujours utiliser security_opt
1security_opt:
2 - no-new-privileges:trueEmpêche les containers d'acquérir de nouveaux privilèges.
3. Labels explicites
Nomme clairement tes routers et middlewares :
1labels:
2 - "traefik.http.routers.myapp-secure.rule=..." # Nom explicite
3 - "traefik.http.routers.myapp-secure.middlewares=security-headers@file,rate-limit@file"4. Healthchecks
Ajoute des healthchecks pour que Traefik route seulement vers des containers sains :
1services:
2 app:
3 healthcheck:
4 test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
5 interval: 30s
6 timeout: 10s
7 retries: 35. Limiter les ressources
1services:
2 app:
3 deploy:
4 resources:
5 limits:
6 cpus: "0.5"
7 memory: 512MIntégration avec Terraform
Si tu as suivi mon article sur Scaleway avec Terraform, tu peux automatiser l'installation de Traefik directement au provisioning de l'instance.
Cloud-init amélioré
Remplace le cloud-init de l'article précédent par cette version qui prépare l'environnement Traefik :
1#cloud-config
2package_update: true
3package_upgrade: true
4
5packages:
6 - apt-transport-https
7 - ca-certificates
8 - curl
9 - git
10 - htop
11 - docker.io
12 - docker-compose-plugin
13 - apache2-utils
14
15runcmd:
16 # Activer Docker
17 - systemctl enable docker
18 - systemctl start docker
19
20 # Créer la structure
21 - mkdir -p /opt/docker/traefik/dynamic /opt/docker/apps
22 - touch /opt/docker/traefik/acme.json
23 - chmod 600 /opt/docker/traefik/acme.json
24
25 # Créer le réseau Docker
26 - docker network create proxy
27
28write_files:
29 - path: /opt/docker/traefik/docker-compose.yml
30 permissions: "0644"
31 content: |
32 services:
33 socket-proxy:
34 image: tecnativa/docker-socket-proxy:0.4
35 restart: unless-stopped
36 environment:
37 CONTAINERS: 1
38 NETWORKS: 1
39 SERVICES: 0
40 TASKS: 0
41 POST: 0
42 volumes:
43 - /var/run/docker.sock:/var/run/docker.sock:ro
44 networks:
45 - socket-proxy
46
47 traefik:
48 image: traefik:v3.6
49 container_name: traefik
50 restart: unless-stopped
51 depends_on:
52 - socket-proxy
53 security_opt:
54 - no-new-privileges:true
55 command:
56 - "--api.dashboard=true"
57 - "--providers.docker=true"
58 - "--providers.docker.endpoint=tcp://socket-proxy:2375"
59 - "--providers.docker.exposedbydefault=false"
60 - "--providers.docker.network=proxy"
61 - "--providers.file.directory=/etc/traefik/dynamic"
62 - "--providers.file.watch=true"
63 - "--entrypoints.web.address=:80"
64 - "--entrypoints.websecure.address=:443"
65 - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
66 - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
67 - "--certificatesresolvers.letsencrypt.acme.email=${email}"
68 - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
69 - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
70 - "--log.level=INFO"
71 ports:
72 - "80:80"
73 - "443:443"
74 volumes:
75 - ./acme.json:/letsencrypt/acme.json
76 - ./dynamic:/etc/traefik/dynamic:ro
77 networks:
78 - proxy
79 - socket-proxy
80 labels:
81 - "traefik.enable=true"
82 - "traefik.http.routers.traefik.rule=Host(`traefik.${domain}`)"
83 - "traefik.http.routers.traefik.entrypoints=websecure"
84 - "traefik.http.routers.traefik.tls=true"
85 - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
86 - "traefik.http.routers.traefik.service=api@internal"
87 - "traefik.http.routers.traefik.middlewares=auth@file,security-headers@file"
88
89 networks:
90 proxy:
91 external: true
92 socket-proxy:
93 driver: bridge
94
95 - path: /opt/docker/traefik/dynamic/middlewares.yml
96 permissions: "0644"
97 content: |
98 http:
99 middlewares:
100 security-headers:
101 headers:
102 browserXssFilter: true
103 contentTypeNosniff: true
104 frameDeny: true
105 stsIncludeSubdomains: true
106 stsPreload: true
107 stsSeconds: 31536000
108 customFrameOptionsValue: "SAMEORIGIN"
109 referrerPolicy: "strict-origin-when-cross-origin"
110 auth:
111 basicAuth:
112 users:
113 - "${traefik_auth}"Variables Terraform
Ajoute ces variables dans ton variables.tf :
1variable "domain" {
2 description = "Nom de domaine principal"
3 type = string
4}
5
6variable "email" {
7 description = "Email pour Let's Encrypt"
8 type = string
9}
10
11variable "traefik_password" {
12 description = "Mot de passe pour le dashboard Traefik"
13 type = string
14 sensitive = true
15}Générer le hash du mot de passe
Dans ton main.tf, utilise le provider htpasswd pour générer le hash :
1terraform {
2 required_providers {
3 # ... autres providers ...
4 htpasswd = {
5 source = "loafoe/htpasswd"
6 version = "~> 1.0"
7 }
8 }
9}
10
11resource "htpasswd_password" "traefik" {
12 password = var.traefik_password
13 salt = substr(sha256(var.traefik_password), 0, 8)
14}
15
16locals {
17 cloud_init_content = templatefile("${path.module}/templates/cloud-init.yml", {
18 domain = var.domain
19 email = var.email
20 traefik_auth = "admin:${htpasswd_password.traefik.bcrypt}"
21 })
22}Démarrer Traefik automatiquement
Ajoute cette commande à la fin de la section runcmd du cloud-init :
1runcmd:
2 # ... commandes précédentes ...
3
4 # Démarrer Traefik (après un délai pour s'assurer que Docker est prêt)
5 - sleep 10
6 - cd /opt/docker/traefik && docker compose up -dRésultat
Après terraform apply, ton instance Scaleway démarre avec :
- Docker et Docker Compose installés
- La structure de fichiers créée
- Le réseau
proxyconfiguré - Traefik lancé et prêt à recevoir des certificats SSL
Il ne te reste plus qu'à pointer ton DNS vers l'IP de l'instance et déployer tes applications.
TLDR
1# 1. Créer le réseau partagé
2docker network create proxy
3
4# 2. Créer la structure
5mkdir -p /opt/docker/traefik/dynamic /opt/docker/apps
6touch /opt/docker/traefik/acme.json
7chmod 600 /opt/docker/traefik/acme.json
8
9# 3. Créer docker-compose.yml et middlewares.yml
10# (voir contenu ci-dessus)
11
12# 4. Générer le mot de passe pour le dashboard
13htpasswd -nB admin
14
15# 5. Démarrer Traefik
16cd /opt/docker/traefik && docker compose up -d
17
18# 6. Déployer une app
19cd /opt/docker/apps/whoami && docker compose up -dPour aller plus loin
- Documentation officielle Traefik
- ACME / Let's Encrypt
- Middlewares disponibles
- Docker Socket Proxy
- Mon article sur l'infrastructure Scaleway
Tu as maintenant une stack de production complète : reverse proxy, SSL automatique, et sécurité renforcée. La prochaine étape serait d'ajouter du monitoring avec Prometheus et Grafana, mais ça c'est pour un prochain article.