Sommaire
Plop,
Solution simple à un problème qui m'a souvent cassé les gonades et dont je suis resté longtemps sans réponse : comment redéployer une stack docker swarm ?
Contexte
Docker est un truc muche permettant de faire tourner des applications dans des containers.
Docker Swarm permet de faire des groupes afin de facilement répartir les containers sur plusieurs serveurs.
Une stack est un groupe cohérent de containers destinés à travailler ensemble.
Exemple
Exemple de stack :
services:
webapp:
image: "webapp"
networks:
- traefik_public
- redis
- pg
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.routers.monservice.rule=Host(`monservice.exemple.org`)"
- "traefik.http.routers.monservice.tls.certresolver=leresolver"
- "traefik.http.services.monservice.loadbalancer.server.port=80"
pg:
image: "postgres:18"
networks:
- pg
volumes:
- pg_data:/var/lib/postgresql
redis:
image: "valkey:latest"
networks:
- redis
networks:
traefik_public:
external: true. Et là c'est le drame.
pg:
redis:
volumes:
pg_data:
puis un petit coup de
$ docker stack deploy -c compose.yml
pis pouf, vous avez 3 containers qui tournent dans votre grappe : une web app, une base de données postgres et un serveur de clé valeur valkey (fork de redis). Y'a des sous réseaux afin d'isoléer tout ce petit monde (valkey ne peut parler qu'à la webapp. pareil pour postgres. valkey et postgres ne peuvent pas se parler entre eux). la db a un volume afin que ses données soient persistantes. La web app est automatiquement géré par le frontal web traefik (avec certificat tls lets encrypt)
Tout est géré automatiquement, y'a rien a faire c'est simple, c'est beau, c'est propre. j'aime. ça a le Jojo seal of approval®, une récompense rare.
Apparté, comment déployer un cluster docker swarm ?
Sur un des serveurs : docker swarm init
Sur les autres : docker swarm join <params donnés par docker swarm init>
Tout est géré automatiquement, y'a rien a faire c'est simple, c'est beau, c'est propre. j'aime. ça a le Jojo seal of approval®, une récompense rare.
Pour les petites infras, docker swarm c'est la vie, mangez en.
Le drame de la CI/CD
Maintenant, imaginez que vous êtes le développeur de webapp.
Vous développez une nouvelle fonctionnalité, vous la pushé, l'intégration continue exécute les tests, construit l'image docker et la publie sur votre registre.
Jusque là pas de problème.
Maintenant, vous voulez redéployer votre stack docker swarm afin de relancer les containers avec des images à jour. Et là c'est le drame.
Parce que voyez-vous, la doc est formelle. le seul moyen de (re-)déployer une stack, c'est via la commande docker stack deploy [-c compose_file]. Commande à exécuter sur un des serveurs de la grappe swarm. C'est à dire pas votre serveur actuel parce que là vous êtes sur celui de la CI.
Arf.
J'vais quand même pas demander à ma CI de se connecter en ssh à ma grappe, télécharger le compose.yaml depuis le dépot et exécuter le docker stack deploy. C'est ignoble et ça a l'air un peu relou à implémenter pour que ça soit suffisamment robuste.
Pendant longtemps ma solution a été d'utiliser un webhook portainer : la CI faisait une requête HTTP sur une URL secrète, et pouf portainer redeploy la stack.
C'était insatisfaisant parce fallait d'abord déclarer la stack dans portainer pour obtenir l'url du webhook à enregister dans la CI. Moi je suis une sale feignasse, je veux que tout marche seul automagiquement:(
Les contextes à la rescousse
Les contextes sont un moyen de gérer plusieurs démons docker depuis un client docker.
Par défault, votre client docker ne connait qu'un seul contexte, default (votre docker local).
jtremesay@nemo ~> docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
Toutes les commandes docker que vous tapez sont envoyées à ce contexte, comme docker run, docker ls, ou cas qui nous intéresse, docker stack deploy.
Et oh joie, vous pouvez déclarer des contextes distants :
$ docker context create --docker "host=ssh://alpha.exemple.org" alpha
Exemple d'utilisation :
# Commande exécuté localement :
$ docker ps
# Changement de contexte :
$ docker context use alpha
# Commande exécuté sur alpha :
$ docker ps
# Restaurer le contexte par défaut :
$ docker context use default
# Utiliser un contexte pour un one-shot:
$ docker --context alpha ps
$ DOCKER_CONTEXT=alpha docker ps
Contextes et CI
Préparation à effectuer sur le nœud docker :
# Création d'un utilisateun dédié à la CI
$ sudo useradd -m -G docker ci
# Se connecte en tant que CI,
# crée une clé SSH, et configure SSH afin d'accepter la
# clé et de n'autoriser que la command
# `docker system dial-stdio`
sudo -u ci -s
ssh-keygen
echo 'command="docker system dial-stdio"' $(cat $HOME/.ssh/id_ed25519.pub) >> $HOME/.ssh/authorized_keys
Exemple avec Github Actions :
jobs:
deploy:
runs-on: ubuntu-latest
needs: build-prod
if: github.ref == 'refs/heads/main'
steps:
- name: Création de la clé privée SSH depuis les secrets
shell: bash
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ssh_private_key }}" > ~/.ssh/private.key
chmod 600 ~/.ssh/private.key
cat > ~/.ssh/config <<EOF
Host docker
HostName alpha.exemple.org
IdentityFile ~/.ssh/private.key
StrictHostKeyChecking no
EOF
- name: Creation du contexte Docker
shell: bash
run: |
docker context create docker --docker "host=ssh://docker"
docker context use docker
- name: Connection à Docker hub (nécessaire si votre image est dans un dépot privé)
uses: docker/login-action@v3
with:
username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }}
- name: Redéployement
shell: bash
run: |
docker stack deploy \
--detach=false \
--with-registry-auth \
--prune \
my_stack
Pour les gens intéressés, j'en ai fait une action dédié. En terme de support, y'a pas de support, donc je vous enjoins à forker joyeusement ou copier directement le code dans votre workflow :)
En travail préliminaire, y'a juste à renseigner la clé SSH dans les secrets de la CI. À vous de voir si vous préférez le faire qu'une fois au niveau global et accessible par tous vos dépots afin de ne plus jamais avoir à y toucher et que ça juste marche ou carrément créer un nouvel user (ou juste clé) par repo pour sécuriser et cloisonner.
Ça n'a pas le jojo seal of approval® parce que j'aime pas trop balader comme ça des clés ssh, mais ça reste simple et efficace, la CI va automatiquement crée la stack au premier push et la redeploy automatiquement lors des pushs suivant, y'a pas d'autres dépendances que docker et ssh.

# Joli
Posté par flagos . Évalué à 2 (+0/-0). Dernière modification le 05 mars 2026 à 17:26.
Pour mon "homelab" sur un VPS, j'utilise docker compose et non swarm (vu qu'il n'y a qu'une seule machine), mais j'ai eu un peu le meme sujet que toi.
Pour ma part, j'ai considéré que le coup de la clé SSH avec droits root sur Github était rédhibitoire, meme pour un homelab. Je suis donc parti en quete d'une version webhook. J’étais tombe notamment sur Gantry qui est specifique a swarm et qui pourrait t’intéresser: Il marche sur un webhook (avec une API key) et docker pull depuis la registry les containers qui ont un certain label. Donc pas besoin de le "configurer" qui est le reproche que tu fais a portainer si j'ai bien compris. Il suffit que tu mettes les labels sur tes containers au déploiement.
Bon moi pour ma part j'ai fait plus simple, j'ai fait un Dockerfile avec le package
webhookavec une API key, et qui fait simplement git pull + docker compose up --build. Je deploie tout ca avec un playbook ansible, j'ai juste une liste de services a déclarer, j'utilise une variable ansible pour ca. Dans l'esprit homelab, c'est bien suffisant, mais j'aurais bien aimer avoir une solution a base de labels.Envoyer un commentaire
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.