Migrer de Nginx Proxy Manager vers Traefik : voyage au pays de la config
En bref
Passer de Nginx Proxy Manager à Traefik pour un reverse proxy 100 % piloté par le code : découverte automatique des services Docker, labels dans les fichiers compose et Let's Encrypt via DNS challenge.
Après des mois passés sur Nginx Proxy Manager (NPM) pour gérer le reverse proxy de mon homelab, j’ai décidé de passer au niveau supérieur. Comprenez-moi bien : NPM est excellent pour démarrer vite grâce à son interface web conviviale. Mais à mesure que mon infrastructure grossissait, je me suis mis à vouloir quelque chose de plus… orienté code. Quelque chose qui se versionne, s’automatise et se déploie sans cliquer dans une interface web.
Place à Traefik.
Pourquoi migrer ?
Avant d’entrer dans les détails techniques, voici pourquoi j’ai choisi Traefik plutôt que NPM :
Infrastructure as Code : tout vit dans Git. Fini la configuration manuelle via une interface web qu’on ne peut ni répliquer ni rollback facilement.
Découverte automatique des services : Traefik repère automatiquement les nouveaux containers Docker et configure les routes à partir des labels. Plus besoin de créer manuellement chaque proxy host.
Intégration Docker native : les labels directement dans les fichiers docker-compose.yaml rendent la configuration auto-documentée et colocalisée avec la définition des services.
SSL/TLS automatisé : gestion des certificats Let’s Encrypt avec DNS challenge, parfait pour des services internes non exposés publiquement.
Meilleure observabilité : dashboard et métriques intégrés.
Le point de départ
Mon infrastructure était organisée en deux stacks principales :
Stack Prism (pixel/prism) : la couche réseau/proxy faisant tourner NPM
- Nginx Proxy Manager sur les ports 80, 443 et 81 (UI d’admin)
- Proxy hosts configurés manuellement via l’interface web
- Certificats Let’s Encrypt gérés par NPM
Stack Contentarium (pixel/contentarium) : les services du serveur multimédia
- Sonarr, Radarr, Plex (tous en
network_mode: host) - Services accédés via un nom de domaine pointant vers une IP locale
Le tout déployé automatiquement par des workflows GitHub Actions sur des runners self-hosted. Une Infrastructure as Code propre et automatisée… sauf cette partie interface web de NPM.
Le plan de migration
L’objectif était simple :
- Remplacer NPM par Traefik dans la stack
prism - Ajouter les labels Traefik à tous les services de la stack
contentarium - Passer de
network_mode: hostà des réseaux bridge Docker - Migrer de la configuration manuelle des proxys vers la découverte automatique des services
- Raccourcir les noms de domaine de
*.home.example.comà*.h.example.com(par souci de concision)
Implémentation
Étape 1 : configuration de Traefik
J’ai d’abord créé un fichier de configuration statique (traefik.yml) :
# API and Dashboardapi: dashboard: true insecure: false
# Entry PointsentryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https permanent: true
websecure: address: ":443" http: tls: certResolver: letsencrypt
# Docker Providerproviders: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false network: proxy-network
# Let's Encrypt with DNS ChallengecertificatesResolvers: letsencrypt: acme: storage: /acme.json dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "8.8.8.8:53"Étape 2 : Docker Compose pour Traefik
Mise à jour de stacks/pixel/prism/compose.yaml :
services: traefik: image: 'traefik:v3.2' container_name: traefik restart: unless-stopped ports: - '80:80' - '443:443' - '8080:8080' # Dashboard environment: - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} - CF_API_EMAIL=${CF_API_EMAIL} - TZ=${TZ} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ${ROOT}/config/traefik/traefik.yml:/etc/traefik/traefik.yml:ro - ${ROOT}/config/traefik/acme.json:/acme.json networks: - proxy-network labels: - "traefik.enable=true" - "traefik.http.routers.dashboard.rule=Host(`proxy.example.com`)" - "traefik.http.routers.dashboard.entrypoints=websecure" - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" - "traefik.http.routers.dashboard.service=api@internal"
networks: proxy-network: external: trueÉtape 3 : labels des services
Pour chaque service de la stack contentarium, j’ai ajouté des labels Traefik. Voici un exemple avec Overseerr :
overseerr: container_name: overseerr image: lscr.io/linuxserver/overseerr:latest environment: - PUID=${PUID} - PGID=${PGID} - TZ=${TZ} volumes: - ${ROOT}/config/overseerr:/config ports: - 5055:5055 restart: unless-stopped networks: - proxy-network labels: - "traefik.enable=true" - "traefik.http.routers.overseerr.rule=Host(`overseerr.example.com`)" - "traefik.http.routers.overseerr.entrypoints=websecure" - "traefik.http.routers.overseerr.tls.certresolver=letsencrypt" - "traefik.http.services.overseerr.loadbalancer.server.port=5055"Les problèmes (oh là là, il y en a eu)
Problème nº 1 : configuration du réseau Docker
Erreur :
network proxy-network was found but has incorrect label com.docker.compose.network set to ""Cause : j’avais créé manuellement le réseau proxy-network avec docker network create, mais Docker Compose s’attendait à le gérer lui-même.
Solution : marquer le réseau comme external: true dans le fichier compose :
networks: proxy-network: external: trueProblème nº 2 : conflit de port
Erreur :
Bind for 0.0.0.0:443 failed: port is already allocatedCause : NPM tournait encore et occupait les ports 80 et 443.
Solution : arrêter NPM manuellement avant de déployer Traefik :
docker stop npm && docker rm npm(Note pour moi-même : ajouter ça au workflow de déploiement pour les prochaines migrations.)
Problème nº 3 : fichier de configuration introuvable
Erreur :
error="command traefik error: read /etc/traefik/traefik.yml: is a directory"Cause : le fichier traefik.yml existait dans mon dépôt Git mais pas sur le serveur. Le volume mount a créé un répertoire au lieu de monter le fichier.
Solution : copier le fichier de configuration sur le serveur manuellement :
Leçon retenue : s’assurer que les fichiers de configuration sont déployés avant le démarrage du container. Ça pourrait être automatisé dans le workflow GitHub Actions à l’avenir.
Problème nº 4 : échec de génération du certificat SSL
Erreur :
Unable to obtain ACME certificate for domainserror="...unable to parse email address"Cause : dans traefik.yml, j’avais utilisé email: "${CF_API_EMAIL}" en m’attendant à une interpolation de la variable d’environnement.
Solution : écrire l’email en dur directement dans traefik.yml :
certificatesResolvers: letsencrypt: acme: storage: /acme.json dnsChallenge: provider: cloudflareLe token de l’API Cloudflare, lui, peut toujours être passé en variable d’environnement au container (Traefik le lit au runtime).
Le résultat
Une fois ces soucis réglés, tout a fonctionné à merveille :
✅ Tous les services accessibles via *.example.com avec des certificats Let’s Encrypt valides
✅ Dashboard Traefik sur proxy.example.com
✅ Découverte automatique des services : ajouter un nouveau service revient à ajouter des labels
✅ Infrastructure as Code complète, toute la configuration du proxy versionnée dans Git
✅ Renouvellement des certificats sans coupure
✅ Configuration propre et lisible, colocalisée avec la définition des services
À retenir
1. Variables d’environnement dans Traefik Les fichiers de configuration statique (.yml) ne supportent pas l’interpolation de variables d’environnement. Seule la configuration au runtime (labels, config dynamique) la supporte.
2. Gestion des réseaux Docker Soyez cohérent : laissez Docker Compose gérer les réseaux (external: false) ou gérez-les à la main (external: true). Ne mélangez pas les deux approches.
3. Montage de fichier ou de répertoire Quand vous montez des fichiers de configuration, assurez-vous qu’ils existent sur l’hôte avant de démarrer les containers. Sinon, Docker crée un répertoire à la place.
4. Conflits de ports Vérifiez toujours les conflits de ports avant de déployer. Ici, arrêter l’ancien proxy (NPM) au préalable était indispensable.
5. Les bénéfices de l’Infrastructure as Code Malgré les embûches de la migration, tout avoir dans Git rend les rollbacks triviaux :
git revert <commit-hash>git push origin mainGitHub Actions redéploie automatiquement la configuration précédente qui fonctionnait.
Est-ce que je le referais ?
Sans hésiter. La migration initiale m’a pris quelques heures (surtout du dépannage), mais les bénéfices sur le long terme valent le coup :
- Fini la configuration manuelle via une interface web
- Infrastructure versionnée avec un historique d’audit complet
- Découverte automatique des services, un gain de temps à chaque nouveau service
- Meilleure observabilité grâce au dashboard intégré
- Déploiement cohérent via GitHub Actions
Si vous faites tourner un homelab avec plusieurs services et que vous êtes déjà sur Docker Compose, Traefik est un choix naturel. La courbe d’apprentissage est plus raide qu’avec NPM, mais le gain en automatisation et en maintenabilité est réel.