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.
Pour aller plus loin : le cours vidéo, l'agent Edge et la bascule de conteneurs
J'ai depuis enregistré un cours complet sur Portainer, de zéro jusqu'aux pratiques d'expert, avec une partie entière sur une question qu'on me pose souvent : comment déplacer ses conteneurs quand un serveur doit être arrêté ? Voici la vidéo, puis les points qui complètent cet article.
L'agent Edge : quand la machine est derrière un pare-feu
Dans mon installation, le serveur Portainer joint chaque agent à travers le VPN. Mais que faire si la machine est chez un client, dans un magasin, derrière une box sur laquelle vous n'avez pas la main, et sans VPN possible ?
C'est le rôle de l'agent Edge. Cette fois, c'est l'agent qui contacte le serveur, à intervalles réguliers, pour demander s'il y a des ordres à exécuter. Aucun port à ouvrir côté machine : seul le serveur Portainer doit être joignable (sur son port 8000 pour le tunnel, en plus de l'interface). Le revers : un léger délai entre l'ordre et son exécution, selon l'intervalle de vérification choisi.
Basculer une stack d'un serveur à l'autre
Un serveur doit être arrêté pour une maintenance, ou vous voulez simplement rééquilibrer la charge. Deux méthodes.
À la main, avec Portainer. Sur une stack, Portainer propose de la dupliquer ou de la migrer vers un autre environnement. Il redéploie la même définition sur la nouvelle machine, puis la retire de l'ancienne.
Le piège, et je préfère prévenir : Portainer déplace la définition des conteneurs, pas leurs données. Les volumes restent sur l'ancienne machine. Avant une migration, il faut soit copier les volumes, soit utiliser un stockage partagé (un partage NFS sur un NAS, par exemple).
# Copier un volume d'une machine à l'autre, via le VPN
docker run --rm -v mon_volume:/data alpine tar czf - -C /data . \
| ssh 10.8.0.3 "docker run --rm -i -v mon_volume:/data alpine tar xzf - -C /data"
Automatiquement, avec Docker Swarm. On regroupe plusieurs machines dans un cluster Swarm, avec l'agent Portainer déployé sur chaque nœud. Si un nœud tombe en panne, Swarm relance tout seul ses services sur les nœuds restants, et Portainer affiche la bascule en direct.
Pour une maintenance prévue, c'est encore plus simple : dans Portainer, on passe le nœud en mode drain. Swarm déplace ses conteneurs sur les autres nœuds, on peut éteindre la machine, puis la repasser en active au retour. Avec plusieurs réplicas, le service ne s'arrête jamais.
# L'équivalent en ligne de commande
docker node update --availability drain serveur-local-1
docker node update --availability active serveur-local-1
Le GitOps avec Portainer
Plutôt que de coller le contenu du docker-compose.yml dans l'éditeur web, une stack peut être créée depuis un dépôt Git. Portainer surveille le dépôt (par interrogation régulière ou via un webhook) et redéploie automatiquement à chaque modification. Le dépôt devient la source de vérité, et chaque changement d'infrastructure passe par un commit relisible.
À plus grande échelle, les stacks Edge déploient une même stack sur des dizaines ou des centaines de machines Edge en une seule opération, ciblées par groupes.
Les équipes et les droits
Portainer gère les utilisateurs et les équipes : chaque équipe ne voit que les environnements qui lui sont attribués. L'édition Business ajoute des rôles plus fins, comme un accès en lecture seule, ou un rôle d'opérateur qui peut redémarrer des conteneurs sans pouvoir en créer.
Les bonnes pratiques que je retiens
- Toujours du HTTPS pour accéder à l'interface (derrière Traefik, dans mon cas).
- Des comptes nominatifs, jamais un compte administrateur partagé : l'historique des actions n'a de valeur que si l'on sait qui a fait quoi.
- Une sauvegarde régulière de la configuration de Portainer (fonction intégrée dans les paramètres, ou sauvegarde du volume
portainer_data). - Des versions d'images fixées en production. J'utilise
latestdans les exemples ci-dessus par simplicité, mais pour une infra qui doit rester stable, fixer la version évite les mauvaises surprises lors d'un redémarrage.
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.