Je viens de changer de serveur dédié, comme je le fais assez régulièrement pour profiter de baisses de tarif ou d’opportunités spécifiques. Aujourd’hui, j’ai tout migré en Docker Compose, avec plusieurs stacks que j’ai souhaité isoler en assignant une IP distincte à chaque usage et à chaque ensemble de conteneurs. Récit d’une migration…
Un serveur, plusieurs identités
Quand on gère un serveur dédié avec plusieurs adresses IP publiques, la tentation est grande de tout faire tourner sur la même interface ou d’avoir assez de ressources pour héberger carrément un Hyperviseur et monter des VMs avec un proxy/firewall frontal un réseau privé interne totalement virtualisé. Aujourd’hui j’ai décidé de procéder différemment, ne travailler qu’avec des stocks docker-compose et des proxy en partie basés sur la brique Traefik que je maitrise bien désormais et que j’aime beaucoup.
J’ai essayé de faire les choses proprement cette fois-ci (pas du lab donc …) : Trois IPs publiques, trois (et plus …) stacks Docker complètement indépendants, trois réseaux distincts. Chaque service a son identité réseau (trois réseau bridges), ses propres règles d’exposition de ports (pour en limiter au maximum l’accessibilité depuis l’extérieur), et surtout, ses propres certificats Let’s Encrypt intégrés différemment suivant les stacks. Le tout sans que chaque environnement ne connaisse l’existence des autres.
Traefik comme pierre angulaire
Si vous me suivez depuis un moment, vous savez que Traefik est un outil que j’utilise quasi systématiquement dans mon lab (je vous prépare un billet spécifique sur Traefik manager, que je vous ai présenté rapidement dans une pépite récemment). Pour rappel rapide : Traefik est un reverse proxy conçu nativement pour Docker et Kubernetes, qui gère automatiquement les certificats Let’s Encrypt et route le trafic selon le nom de domaine (SNI). Du plus, pas besoin de recharger une config à chaque ajout de service. Il détecte les modifications dynamiquement, que ce soit via des labels Docker ou des configurations spécifiques (méthode que je préfère, perso)
Dans cette architecture, chaque stack dispose de sa propre IP, bindée (ouh que c’est horrible comme néologisme …) exclusivement sur son IP publique dédiée. Trois stacks donc, trois IPs, aucune collision possible sur le port 443 (un pour chaque IP, donc). La modification clé par rapport à un déploiement classique est minimaliste mais essentielle : on remplace le binding 0.0.0.0:443 habituel par un binding explicite sur l’IP concernée.
- Le premier Stack est une instance Pangolin, basée en partie sur Traefik justement
- Le second est une instance Mailcow Dockerised, un serveur de messagerie autonome
- Le troisième est une instance maison qui utilise les conteneurs Otterwiki (pour le Wiki de vBlog.io), WordPress (pour vBlog.io lui-même) et Mariadb pour la bdd en backend.
- Enfin, stack technique par dessus pour le reverse proxy Traefik associé au stack vBlog.
Mailcow sur son IP dédiée, et c’est non négociable
Mailcow Dockerised, c’est une stack mail complète comme je l’ai déjà dit : Postfix, Dovecot, Rspamd, etc, le tout packagé proprement dans une composition docker-compose C’est l’un des projets open source les plus sérieux dans l’univers de l’auto-hébergement mail, que j’utilise depuis des années.
Mais le mail, c’est une discipline à part. La réputation d’une IP mail se construit sur des mois et se détruit en quelques heures. Il faut un enregistrement PTR correct, un SPF propre, un DKIM signé, un DMARC en place. Et tout ça doit pointer vers une seule IP, cohérente, dédiée à cet usage. Mélanger le trafic mail avec du trafic web sur la même IP, c’est la garantie de se retrouver en blacklist un jour ou l’autre, souvent au pire moment.
Mailcow gère nativement ses propres certificats et son propre binding réseau. La configuration est donc différente des deux autres stacks : on ne pose pas un Traefik devant Mailcow (même si on peut en bidouillant). On configure directement Mailcow pour qu’il écoute exclusivement sur l’IP dédiée, via son fichier mailcow.conf dont je vous fournis l’extrait important :
|
1 2 3 4 5 6 |
# mailcow.conf MAILCOW_HOSTNAME=mail.domain.tld HTTP_BIND=57.129.101.5 HTTPS_BIND=57.129.101.5 HTTP_PORT=80 HTTPS_PORT=443 |
Le réseau interne de Mailcow reste entièrement isolé. Aucun pont avec les autres stacks. Les ports SMTP, IMAP, HTTPS sont exposés uniquement sur cette IP. C’est tout.
Pangolin dans son coin
Pangolin, pour ceux qui ne connaissent pas encore, est un système de tunneling et d’exposition de services internes, une alternative sérieuse à Cloudflare Tunnel ou Tailscale pour ceux qui veulent garder la maîtrise de leur infrastructure. C’est le type de service qui mérite une isolation maximale : c’est un point d’entrée vers des services internes, au sein de mon vLab, dont potentiellement sensibles.
Une instance Traefik dédiée, bindée sur la première IP, avec son propre réseau Docker interne. Pangolin n’a aucune visibilité sur le stack mail, ni sur le stack général. Le cloisonnement est total.
Pour info, voici le docker-compose.yml que j’ai utilisé :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
name: pangolin services: pangolin: image: docker.io/fosrl/pangolin:latest container_name: pangolin restart: unless-stopped user: 1010:1010 volumes: - ./config:/app/config healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] interval: "10s" timeout: "10s" retries: 15 gerbil: image: docker.io/fosrl/gerbil:latest container_name: gerbil restart: unless-stopped user: 1010:1010 depends_on: pangolin: condition: service_healthy command: - --reachableAt=http://gerbil:3004 - --generateAndSaveKeyTo=/var/config/key - --remoteConfig=http://pangolin:3001/api/v1/ volumes: - ./config/:/var/config cap_add: - NET_ADMIN - SYS_MODULE ports: - 51.89.87.169:51820:51820/udp - 51.89.87.169:21820:21820/udp - 51.89.87.169:443:443 - 51.89.87.169:80:80 traefik: image: docker.io/traefik:v3.6 container_name: pangolin-traefik restart: unless-stopped user: 1010:1010 network_mode: service:gerbil depends_on: pangolin: condition: service_healthy command: - --configFile=/etc/traefik/traefik_config.yml volumes: - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs environment: - OVH_ENDPOINT=ovh-eu - OVH_APPLICATION_KEY=xxxxxxxxxxx - OVH_APPLICATION_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - OVH_CONSUMER_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - LEGO_DISABLE_CNAME_SUPPORT=true networks: default: driver: bridge name: pangolin |
Le stack custom pour vblog.io
Enfin, le dernier stack , c’est pour vblog.Io :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
services: mariadb: image: mariadb:12 container_name: mariadb user: 1005:1005 volumes: - ./data:/var/lib/mysql restart: unless-stopped networks: - traefik-net # phpmyadmin: # image: phpmyadmin:latest # container_name: phpmyadmin # restart: unless-stopped # user: 1030:1030 # depends_on: # - mariadb # environment: # - PMA_HOST=mariadb # - PMA_PORT=3306 # - UPLOAD_LIMIT=1G wordpress: image: wordpress restart: unless-stopped container_name: vblog user: 1011:1011 depends_on: - mariadb env_file: - .env volumes: - ./wordpress:/var/www/html networks: - traefik-net otterwiki: image: redimp/otterwiki:2 restart: unless-stopped user: 1012:1012 volumes: - ./app-data:/app-data networks: - traefik-net networks: traefik-net: external: true |
Un stack relativement simple, qui est utilisé depuis Traefik, via un docker-compose autonome connecté au réseau bridge traefik-net dont voici le manifest compose également :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
services: traefik: image: traefik:v3 container_name: traefik restart: unless-stopped user: 1000:1000 ports: - "51.77.83.135:80:80" - "51.77.83.135:443:443" volumes: - ./traefik.yaml:/etc/traefik/traefik.yaml:ro - ./dynamic:/etc/traefik/dynamic - ./acme.json:/etc/traefik/certs/acme.json - ./log:/var/log/traefik environment: - OVH_ENDPOINT=ovh-eu - OVH_APPLICATION_KEY=xxxxxxxxxx - OVH_APPLICATION_SECRET=xxxxxxxxxxxxxxxxxxxx - OVH_CONSUMER_KEY=xxxxxxxxxxxxxxxxxxxx - LEGO_DISABLE_CNAME_SUPPORT=true networks: - traefik-net networks: traefik-net: external: true |
Bref, tout cela est relativement simple, mais l’important c’est que tout cohabite sur des IPs différentes au sein de la même machine bare metal.
La tuyauterie, c’est là que ça se complique un chouya
On parle beaucoup des services, des interfaces, des fonctionnalités. Mais la vraie solidité d’une architecture Docker tient dans les détails qu’on ne voit pas : les réseaux, les utilisateurs, les ports exposés.
Chaque stack tourne avec un utilisateur Linux dédié, non-root, créé explicitement sur l’hôte. Le paramètre user: dans le docker-compose pointe vers cet uid. Si un conteneur est compromis, l’attaquant se retrouve avec les droits d’un utilisateur sans privilèges, sans accès aux autres stacks, sans accès aux répertoires système.
Enfin, aucun port applicatif n’est publié directement. Pas de 0.0.0.0:8080:8080 qui traîne dans un compose et qui expose un service en HTTP brut sur toutes les interfaces et vers le monde entier (même si j’aime partager ^^).
En définitive c’est presque un un petit pseudo Kube artisanal (dans son concept) … plus simple à gérer qu’un K3S ou un Minikube, parfait pour ceux qui veulent se préparer techniquement à Kubernbetes. C’est un peu plus de travail à l’installation, mais c’est le genre d’architecture avec laquelle on dort bien la nuit car avant tout c’est à échelle humaine (encore un peu disons …).
Pour ne rien gâcher, docker reste un modèle de maintenabilité autant pour chacun des stacks déployés que pour le serveur lui même, dans son ensemble.
Pour terminer, sachez que fail2ban sur le serveurs sécurise en plus le tout !
Vous pensez que j’ai oublié des éléments de sécu importants, qu’il serait judicieux d’ajouter certains composants pour améliorer le setup initial et sa sécurité générale ? n’hésitez pas à me le dire dans les commentaires et bon Jeudi de l’ascension !
