Journal Docker swarm et CI, les contextes à la rescousse

Posté par  (site web personnel) . Licence CC By‑SA.
16
3
mar.
2026

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  . Évalué à 5 (+3/-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 webhook avec 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.

    • [^] # Re: Joli

      Posté par  (site web personnel) . Évalué à 4 (+2/-0).

      j'utilise docker compose et non swarm

      à l'époque où j'avais monté ce serveur, docker compose était présenté comme un truc à utilisé uniquement pour les développements. Et vu la simplicité de Swarm (même avec un seul node), j'ai pas eu à changer mes habitudes.

      Donc pas besoin de le "configurer" qui est le reproche que tu fais a portainer si j'ai bien compris.

      Ça me faisait déjà chier d'avoir une dépendance aussi lourde juste pour gérer le redeploy. Mais ouais, en plus le fait de devoir pré-créer la stack dans portainer m'a incité à chercher une solution plus simple

      J’étais tombe notamment sur Gantry qui est specifique a swarm et qui pourrait t’intéresser

      Merci !

      Pour ma part, j'ai considéré que le coup de la clé SSH avec droits root sur Github était rédhibitoire

      tu noteras quand même que dans ma config la clé SSH ne permet que de se connecter à un simple utilisateur qui n'a accès qu'à un sous ensemble de docker.

      mais ouais, comme j'ai pas encore configuré docker ou podman en rootless; si quelqu'un choppe la clé, il peut très bien faire docker --context monserveur run -it -v /:/mnt ubuntu et là je suis mort :D

  • # Je le croyais mort... mais non !

    Posté par  . Évalué à 6 (+4/-0).

    J'étais arrêté au fait que Swarm était mort des années de cela.

    Je n'ai suivi les actus Docker que de très loin, la vie m'ayant emmené plutôt côté Podman avec lequel je ne fais franchement rien de compliqué ou de fou fou.

    Au risque d'exposer mon cruel manque de culture, j'étais donc un peu resté sur docker/podman-compose d'un côté et Kubernetes/Nomad de l'autre (peut-être entretenu par les outils comme Rancher Desktop ou Podman Desktop). Avec des trucs comme dokku au milieu.

    Enfin bref, tout ça pour commenter que je ne savais pas que Docker Swarm existait encore et que celui-qui-est-mort s'appelle finalement Docker Classic Swarm.

    Ce que la doc précise bien :

    Do not confuse Docker Swarm mode with Docker Classic Swarm which is no longer actively developed

    Donc : je te remercie de m'avoir mis à jour !

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.