Nuit Blanche
· 6 min de lecture

Migrer de Nginx Proxy Manager vers Traefik : voyage au pays de la config

#homelab #proxy #cicd
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 :

  1. Remplacer NPM par Traefik dans la stack prism
  2. Ajouter les labels Traefik à tous les services de la stack contentarium
  3. Passer de network_mode: host à des réseaux bridge Docker
  4. Migrer de la configuration manuelle des proxys vers la découverte automatique des services
  5. 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 Dashboard
api:
dashboard: true
insecure: false
# Entry Points
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
# Docker Provider
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: proxy-network
# Let's Encrypt with DNS Challenge
certificatesResolvers:
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: true

Problème nº 2 : conflit de port

Erreur :

Bind for 0.0.0.0:443 failed: port is already allocated

Cause : NPM tournait encore et occupait les ports 80 et 443.

Solution : arrêter NPM manuellement avant de déployer Traefik :

Terminal window
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 domains
error="...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:
email: [email protected] # Hardcoded, not ${CF_API_EMAIL}
storage: /acme.json
dnsChallenge:
provider: cloudflare

Le 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 :

Terminal window
git revert <commit-hash>
git push origin main

GitHub 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.

Ressources

Articles liés