Portainer : piloter 4 serveurs Docker depuis une seule interface, en toute securite via VPN

Conteneurs DevOps Docker Infrastructure Portainer VPN
Portainer : piloter 4 serveurs Docker depuis une seule interface, en toute securite via VPN

Au début, je gérais mes serveurs comme tout le monde : un terminal par machine, des alias SSH dans mon ~/.ssh/config, et une mémoire approximative de "c'est sur quel serveur déjà ce conteneur ?". Ça tenait sur deux machines. Sur quatre, ça devient vite ingérable. Je confondais les VPS, je relançais des conteneurs sur la mauvaise machine, et un soir j'ai passé vingt minutes à chercher pourquoi mon service n'était pas joignable — il tournait, mais sur le mauvais serveur.

C'est pour ça que j'ai mis en place Portainer. Aujourd'hui je pilote 4 machines depuis une seule interface — 2 VPS et 2 serveurs locaux — sans jamais ouvrir un terminal SSH pour déployer un conteneur. Et le tout est sécurisé via VPN : aucun des agents n'est exposé sur Internet.

Ce qu'est Portainer en vrai

Portainer c'est une interface web pour Docker. Ça semble banal dit comme ça, mais la vraie valeur vient du mode multi-environnements avec les agents. L'idée : vous installez Portainer sur une machine centrale, et vous déployez un agent léger sur chaque machine à piloter. L'agent communique avec le serveur central. Depuis votre navigateur, vous voyez tous vos environnements dans une seule vue, vous déployez des stacks, vous lisez les logs, vous redémarrez des conteneurs — tout ça sans SSH.

Il existe deux éditions : Community Edition (gratuite, ce que j'utilise) et Business Edition (payante, avec des fonctionnalités RBAC avancées et du support). Pour un usage personnel ou une petite équipe, la CE fait largement le boulot.

L'architecture que j'utilise

Un serveur Portainer central qui tourne sur mon serveur local principal. Quatre agents — un par machine — qui se connectent à ce serveur. La communication se fait uniquement via VPN : les agents n'écoutent que sur l'interface réseau VPN, pas sur l'IP publique. Depuis l'extérieur, il n'y a rien d'accessible.

[ Navigateur ]
      |
      | HTTPS via Traefik
      v
[ Portainer Server ] ← ma machine locale principale
      |
      | TCP 9001 via VPN (WireGuard)
      ├── [ Agent VPS 1 ]    10.8.0.2
      ├── [ Agent VPS 2 ]    10.8.0.3
      └── [ Agent Serveur local 2 ] 10.8.0.4

Aucun port 9001 n'est ouvert sur les interfaces publiques des VPS. Si quelqu'un scanne les ports de mes VPS, il ne verra rien lié à Portainer. Seules les machines connectées au VPN peuvent joindre les agents.

Installer le serveur Portainer

Sur la machine centrale, en stack Portainer (oui, Portainer qui se gère lui-même) :

version: "3"

services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.arnaudallouche.fr`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"

volumes:
  portainer_data:

networks:
  default:
    external:
      name: traefik_web

Le volume sur /var/run/docker.sock donne à Portainer l'accès au daemon Docker local — c'est ce qui lui permet de gérer les conteneurs sur cette machine. C'est aussi son environnement "local" par défaut dans l'interface.

Déployer les agents — la partie sécurité

Sur chaque machine distante, l'agent se déploie avec une seule commande. La clé, c'est le paramètre de bind sur l'interface VPN.

D'abord, récupérer l'IP VPN de la machine :

ip addr show wg0 | grep "inet " | awk '{print $2}' | cut -d/ -f1
# → 10.8.0.2 (exemple)

Puis lancer l'agent en liant le port uniquement à cette IP :

docker run -d \
  -p 10.8.0.2:9001:9001 \
  --name portainer_agent \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /var/lib/docker/volumes:/var/lib/docker/volumes \
  portainer/agent:latest

Le -p 10.8.0.2:9001:9001 c'est toute la sécurité. Sans ça, Docker bind le port sur toutes les interfaces — y compris l'IP publique du VPS. Avec ça, le port 9001 n'est accessible que depuis le réseau VPN. Un nmap depuis Internet ne verra rien.

Pour vérifier que c'est bien lié à la bonne interface :

ss -tlnp | grep 9001
# → 0.0.0.0:9001 = mauvais, exposé partout
# → 10.8.0.2:9001 = correct, uniquement sur le VPN

Ajouter les environnements dans Portainer

Une fois les agents lancés, dans l'interface Portainer : Settings → Environments → Add environment → Agent.

Vous donnez un nom à l'environnement et renseignez l'adresse de l'agent : 10.8.0.2:9001. Portainer établit la connexion depuis le serveur central (qui est sur le même VPN) vers l'agent. Si la connexion échoue, c'est presque toujours une règle de firewall qui bloque le port 9001 sur l'interface VPN — ufw allow in on wg0 to any port 9001 règle ça.

Résultat : dans la sidebar, vous avez la liste de vos environnements. Un clic, vous êtes sur la machine. Vous voyez les conteneurs en cours, les images, les volumes, les réseaux. Tout.

Déployer des stacks sur n'importe quelle machine

Le vrai gain au quotidien, c'est le déploiement de stacks. Dans Portainer, une "stack" c'est un docker-compose.yml que vous déployez sur un environnement cible.

Mon workflow : je sélectionne l'environnement cible dans Portainer, je vais dans Stacks → Add stack, je colle mon docker-compose ou je pointe vers un dépôt Git, et je déploie. Pas de SSH, pas de docker compose up -d à distance, pas à gérer les fichiers .env manuellement sur la machine distante — les variables d'environnement sont renseignées directement dans l'interface Portainer.

Ce qui change vraiment quand on gère plusieurs machines : la répartition des charges. Avant Portainer, mes services finissaient tous sur le même VPS parce que c'était le plus simple à atteindre. Avec Portainer, déployer sur le VPS 2 ou le serveur local prend exactement le même nombre de clics. Du coup je répartis vraiment : les services critiques sur les VPS avec leur garantie de disponibilité, les services lourds ou expérimentaux sur les serveurs locaux, les bases de données proches des apps qui les utilisent.

Les fonctionnalités que j'utilise vraiment

Les logs en temps réel. Cliquer sur un conteneur, onglet Logs, et vous voyez le stdout en live. C'est trivial mais c'est ce que j'utilisais le plus souvent avec SSH de toute façon — maintenant c'est dans le navigateur sans connexion à ouvrir.

La console exec. Accès shell dans un conteneur directement depuis le navigateur. Pratique pour déboguer sans avoir à enchaîner ssh machine && docker exec -it container bash.

Les webhooks de déploiement. Chaque stack peut avoir un webhook. Mon pipeline CI/CD appelle ce webhook après un build réussi, et Portainer redémarre la stack avec la nouvelle image. Zéro intervention manuelle pour les mises à jour.

La gestion des images. Voir toutes les images sur une machine, supprimer celles qui ne sont plus utilisées, voir la taille qu'elles occupent. J'ai retrouvé 12 GB d'images orphelines sur un VPS que je n'aurais jamais pensé à nettoyer manuellement.

Ce qu'il faut anticiper

La connexion agent-serveur est continue. Si votre VPN tombe, Portainer perd la connexion aux agents distants et affiche les environnements en "down". Les conteneurs continuent de tourner — Portainer ne gère pas les conteneurs, il les pilote — mais vous perdez la visibilité jusqu'à ce que le VPN revienne. Sur des VPS avec WireGuard bien configuré ça n'arrive presque jamais, mais sur des connexions moins stables ça peut être agaçant.

Portainer n'est pas un orchestrateur. Il ne fait pas de failover automatique, il ne redémarre pas un service sur une autre machine si l'une tombe. Pour ça il faut Swarm ou Kubernetes. Portainer peut gérer des clusters Swarm et des clusters K8s, mais c'est un autre niveau de complexité. Pour mes 4 machines indépendantes, l'approche simple suffit largement.

La version Community Edition limite à 3 environnements dans certaines configurations. En pratique avec la CE récente je n'ai jamais eu ce problème sur 4 machines, mais si vous montez en nombre d'environnements, vérifiez les limitations de votre version avant de tout construire dessus.

La configuration VPN que j'utilise

J'utilise WireGuard avec un serveur central (sur le même VPS que Portainer) et un peer par machine. Chaque machine a une IP fixe dans le réseau VPN : 10.8.0.x. La configuration est standard WireGuard, rien de spécifique à Portainer.

Le point important : le serveur Portainer doit être capable d'atteindre les IPs VPN des agents. Donc soit Portainer tourne sur le serveur WireGuard lui-même, soit sur une machine qui est peer du réseau. Dans mon cas, mon serveur local principal est à la fois serveur WireGuard et serveur Portainer — tout le trafic de gestion passe par lui.

Si vous n'avez pas encore de VPN entre vos machines, WireGuard est la solution la plus simple à mettre en place. Dix minutes de configuration, et vous avez un réseau privé entre toutes vos machines avec du chiffrement moderne.

Ce que ça a changé concrètement

Je déploie plus souvent parce que c'est moins pénible. C'est bête mais c'est la vraie raison. Avant, déployer un nouveau service sur le VPS 2 impliquait de chercher les credentials SSH, de se connecter, de créer le dossier, de copier le compose file, de créer le .env, de lancer le déploiement. Maintenant c'est : sélectionner l'environnement, coller le compose, renseigner les variables, déployer. Deux minutes.

Je gère aussi mieux la répartition des ressources. Je vois en un coup d'œil l'utilisation CPU et RAM de chaque machine depuis le dashboard Portainer. Quand un nouveau service est lourd, je choisis consciemment où le mettre plutôt que de tout mettre par défaut sur la machine la plus facile d'accès.

Le setup initial — installer les agents, configurer le VPN, ajouter les environnements — m'a pris une après-midi. C'est un investissement que j'ai récupéré en quelques semaines. Si vous gérez plus d'une machine Docker, ça vaut la peine.

Retour aux articles Conteneurisation